@ulu/frontend-vue 0.1.0-beta.9 → 0.1.1-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 (99) hide show
  1. package/dist/{breakpoints-BbkGNxxt.js → breakpoints-DM-CBTtb.js} +1 -1
  2. package/dist/frontend-vue.css +1 -1
  3. package/dist/frontend-vue.js +79 -68
  4. package/dist/index-BNRZ3Apw.js +7541 -0
  5. package/lib/components/collapsible/UluAccordion.vue +71 -53
  6. package/lib/components/collapsible/UluAccordionGroup.vue +54 -0
  7. package/lib/components/collapsible/UluCollapsible.vue +144 -0
  8. package/lib/components/collapsible/UluDropdown.vue +29 -29
  9. package/lib/components/collapsible/UluOverflowPopover.vue +1 -1
  10. package/lib/components/elements/UluBadge.vue +51 -28
  11. package/lib/components/elements/UluBadgeStack.vue +8 -13
  12. package/lib/components/elements/UluButtonVerbose.vue +119 -0
  13. package/lib/components/elements/UluCard.vue +1 -1
  14. package/lib/components/elements/UluDefinitionList.vue +14 -17
  15. package/lib/components/elements/UluExternalLink.vue +21 -27
  16. package/lib/components/elements/UluIcon.vue +11 -1
  17. package/lib/components/elements/UluList.vue +53 -55
  18. package/lib/components/elements/UluSpokeSpinner.vue +12 -18
  19. package/lib/components/elements/UluTag.vue +35 -35
  20. package/lib/components/forms/UluFileDisplay.vue +49 -31
  21. package/lib/components/forms/UluFormFile.vue +37 -24
  22. package/lib/components/forms/UluFormMessage.vue +13 -10
  23. package/lib/components/forms/UluFormSelect.vue +28 -16
  24. package/lib/components/forms/UluFormText.vue +24 -15
  25. package/lib/components/forms/UluSearchForm.vue +11 -10
  26. package/lib/components/forms/UluSelectableMenu.vue +99 -0
  27. package/lib/components/index.js +4 -3
  28. package/lib/components/layout/UluTitleRail.vue +18 -0
  29. package/lib/components/layout/UluWhenBreakpoint.vue +9 -0
  30. package/lib/components/navigation/UluBreadcrumb.vue +9 -2
  31. package/lib/components/navigation/UluMenu.vue +8 -3
  32. package/lib/components/navigation/UluMenuStack.vue +3 -1
  33. package/lib/components/navigation/UluPager.vue +102 -0
  34. package/lib/components/systems/facets/ExampleFacetsWithPagination.vue +119 -0
  35. package/lib/components/systems/facets/UluFacetsFilterLists.vue +91 -0
  36. package/lib/components/systems/facets/UluFacetsFilterPopovers.vue +125 -0
  37. package/lib/components/systems/facets/UluFacetsFilterSelects.vue +71 -0
  38. package/lib/components/systems/facets/UluFacetsHeaderLayout.vue +24 -0
  39. package/lib/components/systems/facets/UluFacetsList.vue +62 -34
  40. package/lib/components/systems/facets/UluFacetsResults.vue +63 -0
  41. package/lib/components/systems/facets/UluFacetsSearch.vue +27 -50
  42. package/lib/components/systems/facets/UluFacetsSidebarLayout.vue +70 -0
  43. package/lib/components/systems/facets/UluFacetsSort.vue +45 -0
  44. package/lib/components/systems/facets/_facets.scss +2 -3
  45. package/lib/components/systems/facets/_mock-data.js +40 -0
  46. package/lib/components/systems/facets/useFacets.js +268 -0
  47. package/lib/components/systems/index.js +13 -2
  48. package/lib/components/systems/scroll-anchors/UluScrollAnchors.vue +2 -1
  49. package/lib/components/systems/skeleton/UluShowSkeleton.vue +9 -8
  50. package/lib/components/systems/skeleton/UluSkeletonContent.vue +39 -43
  51. package/lib/components/systems/skeleton/UluSkeletonMedia.vue +4 -6
  52. package/lib/components/systems/skeleton/UluSkeletonText.vue +27 -0
  53. package/lib/components/systems/slider/UluImageSlideShow.vue +1 -1
  54. package/lib/components/systems/slider/UluSlideShow.vue +8 -3
  55. package/lib/components/systems/table-sticky/UluTableSticky.vue +7 -7
  56. package/lib/components/systems/table-sticky/UluTableStickyTable.vue +3 -3
  57. package/lib/components/visualizations/UluAnimateNumber.vue +7 -1
  58. package/lib/components/visualizations/UluProgressBar.vue +99 -68
  59. package/lib/components/visualizations/UluProgressCircle.vue +146 -0
  60. package/lib/components/visualizations/progress-bar-examples.html +175 -0
  61. package/lib/composables/index.js +3 -1
  62. package/lib/composables/useDocumentTitle.js +61 -0
  63. package/lib/composables/usePagination.js +122 -0
  64. package/lib/index.js +1 -0
  65. package/lib/plugins/core/index.js +6 -1
  66. package/lib/plugins/popovers/UluPopover.vue +8 -3
  67. package/lib/plugins/toast/UluToast.vue +1 -1
  68. package/lib/plugins/toast/UluToastDisplay.vue +19 -2
  69. package/lib/utils/dom.js +12 -0
  70. package/lib/utils/index.js +2 -0
  71. package/lib/utils/{vue-router.js → router.js} +114 -30
  72. package/package.json +17 -11
  73. package/types/components/systems/facets/_mock-data.d.ts +18 -0
  74. package/types/components/systems/facets/_mock-data.d.ts.map +1 -0
  75. package/types/components/systems/facets/useFacets.d.ts +39 -0
  76. package/types/components/systems/facets/useFacets.d.ts.map +1 -0
  77. package/types/components/systems/index.d.ts +1 -1
  78. package/types/composables/index.d.ts +2 -0
  79. package/types/composables/useDocumentTitle.d.ts +22 -0
  80. package/types/composables/useDocumentTitle.d.ts.map +1 -0
  81. package/types/composables/usePageTitle.d.ts +19 -0
  82. package/types/composables/usePageTitle.d.ts.map +1 -0
  83. package/types/composables/usePagination.d.ts +25 -0
  84. package/types/composables/usePagination.d.ts.map +1 -0
  85. package/types/index.d.ts +1 -0
  86. package/types/plugins/core/index.d.ts.map +1 -1
  87. package/types/utils/dom.d.ts +1 -0
  88. package/types/utils/dom.d.ts.map +1 -1
  89. package/types/utils/index.d.ts +3 -0
  90. package/types/utils/index.d.ts.map +1 -0
  91. package/types/utils/router.d.ts +144 -0
  92. package/types/utils/router.d.ts.map +1 -0
  93. package/dist/index-D3Uc6T5M.js +0 -6469
  94. package/lib/components/collapsible/UluCollapsibleRegion.vue +0 -278
  95. package/lib/components/forms/UluCheckboxMenu.vue +0 -36
  96. package/lib/components/systems/facets/UluFacets.vue +0 -380
  97. package/lib/components/systems/skeleton/UluSkeletonTextInline.vue +0 -9
  98. package/lib/components/visualizations/UluProgressDonut.vue +0 -97
  99. package/lib/utils/placeholder.js +0 -6
@@ -0,0 +1,268 @@
1
+ import { ref, computed, watch } from 'vue';
2
+ import Fuse from 'fuse.js';
3
+
4
+ /**
5
+ * Generates facet groups and their possible values from a list of items.
6
+ * @param {Array<Object>} allItems - The full list of items.
7
+ * @param {Array<Object>} facetFields - Configuration for which fields to create facets from.
8
+ * @returns {Array<Object>} The generated facet structure.
9
+ */
10
+ function generateInitialFacets(allItems, facetFields) {
11
+ if (!facetFields || !Array.isArray(facetFields)) return [];
12
+ return facetFields.map(group => {
13
+ const allValues = new Set();
14
+ const getValue = group.getValue || (item => item[group.uid]);
15
+
16
+ allItems.forEach(item => {
17
+ const value = getValue(item);
18
+ if (Array.isArray(value)) {
19
+ value.forEach(v => v && allValues.add(v));
20
+ } else if (value) {
21
+ allValues.add(value);
22
+ }
23
+ });
24
+
25
+ const getLabel = group.getLabel || (value => value);
26
+
27
+ const children = [...allValues].map(value => ({
28
+ uid: value,
29
+ label: getLabel(value),
30
+ selected: false
31
+ }));
32
+
33
+ // Sort by label, alphabetically
34
+ children.sort((a, b) => String(a.label).localeCompare(String(b.label)));
35
+
36
+ return {
37
+ ...group,
38
+ children
39
+ };
40
+ });
41
+ }
42
+
43
+
44
+ /**
45
+ * A composable for handling client-side faceted search, filtering, and sorting.
46
+ * @param {import('vue').Ref<Array<Object>>} allItems - A Vue ref containing the full list of items to be processed.
47
+ * @param {Object} options - Configuration options for the composable.
48
+ * @param {Array} [options.initialFacets] - The initial configuration for the facets. Can be generated automatically if `facetFields` is provided.
49
+ * @param {Array} [options.facetFields] - A simpler configuration to automatically generate facets from items. Each item can have `uid`, `name`, `open`, `getValue` and `getLabel`.
50
+ * @param {String} [options.initialSearchValue=''] - The initial value for the search input.
51
+ * @param {String} [options.initialSortType='az'] - The initial sort type.
52
+ * @param {Boolean} [options.noDefaultSorts=false] - If true, the default 'A-Z' and 'Z-A' sorts will not be included.
53
+ * @param {Object} [options.extraSortTypes={}] - Additional sort types to be merged with the default ones.
54
+ * @param {Object} [options.searchOptions={}] - Configuration options for Fuse.js.
55
+ * @param {Function} [options.getItemFacet] - A function to retrieve facet information from an item. Should always return an array of values.
56
+ * @param {Function} [options.getSortValue] - A function to get the value to sort by from an item.
57
+ */
58
+ export function useFacets(allItems, options = {}) {
59
+ const defaultGetItemFacet = (item, uid) => {
60
+ const value = item[uid];
61
+ if (value === null || typeof value === 'undefined') return [];
62
+ return Array.isArray(value) ? value : [value];
63
+ };
64
+
65
+ const {
66
+ initialFacets,
67
+ facetFields,
68
+ initialSearchValue = '',
69
+ initialSortType = 'az',
70
+ noDefaultSorts = false,
71
+ extraSortTypes = {},
72
+ searchOptions: initialSearchOptions = {},
73
+ getItemFacet = defaultGetItemFacet,
74
+ getSortValue = item => (item.title || item.label || ""),
75
+ countMode = 'none' // 'none', 'simple', 'intuitive'
76
+ } = options;
77
+
78
+ const sortAlpha = items => {
79
+ return items.sort((a, b) => {
80
+ const va = getSortValue(a);
81
+ const vb = getSortValue(b);
82
+ if (va && vb) {
83
+ return String(va).localeCompare(String(vb));
84
+ } else {
85
+ return va ? -1 : vb ? 1 : 0;
86
+ }
87
+ });
88
+ }
89
+ const defaultSorts = {
90
+ az: { text: "A-Z", sort: sortAlpha },
91
+ za: { text: "Z-A", sort: items => sortAlpha(items).reverse() },
92
+ };
93
+
94
+ // --- Helpers ---
95
+ function createFacets(initial) {
96
+ return (initial || []).map(group => ({
97
+ ...group,
98
+ open: group.open || false,
99
+ children: group.children.map(facet => ({
100
+ ...facet,
101
+ selected: facet.selected || false
102
+ })),
103
+ selectedCount: 0
104
+ }));
105
+ }
106
+
107
+ // --- State ---
108
+ const facets = ref([]);
109
+ const searchValue = ref(initialSearchValue);
110
+ const selectedSort = ref(initialSortType);
111
+
112
+ // --- Computed ---
113
+ const generatedFacets = computed(() => {
114
+ if (!facetFields || !allItems.value?.length) return null;
115
+ return generateInitialFacets(allItems.value, facetFields);
116
+ });
117
+
118
+ const sortTypes = computed(() => ({
119
+ ...(noDefaultSorts ? {} : defaultSorts),
120
+ ...extraSortTypes
121
+ }));
122
+
123
+ const searchOptions = computed(() => ({
124
+ shouldSort: true,
125
+ keys: ["title", "label", "description", "author"],
126
+ ...initialSearchOptions
127
+ }));
128
+
129
+ const searchedItems = computed(() => {
130
+ if (!searchValue.value?.length) {
131
+ return allItems.value;
132
+ }
133
+ const fuse = new Fuse(allItems.value, searchOptions.value);
134
+ return fuse.search(searchValue.value).map(result => result.item);
135
+ });
136
+
137
+ const selectedFacets = computed(() => {
138
+ const selected = [];
139
+ facets.value.forEach((group) => {
140
+ const selectedChildren = group.children.filter(child => child.selected);
141
+ group.selectedCount = selectedChildren.length;
142
+ if (selectedChildren.length > 0) {
143
+ selected.push({ ...group, children: selectedChildren });
144
+ }
145
+ });
146
+ return selected;
147
+ });
148
+
149
+ const filteredItems = computed(() => {
150
+ return getFilteredItems(searchedItems.value, selectedFacets.value);
151
+ });
152
+
153
+ const displayItems = computed(() => {
154
+ const sortFn = sortTypes.value[selectedSort.value]?.sort;
155
+ if (typeof sortFn !== 'function') {
156
+ return filteredItems.value;
157
+ }
158
+ return sortFn([...filteredItems.value]);
159
+ });
160
+
161
+ // --- Methods ---
162
+ function getFilteredItems(items, selected) {
163
+ if (!selected.length) return items;
164
+ return items.filter(item => {
165
+ return selected.every(group => {
166
+ const itemFacetValues = getItemFacet(item, group.uid);
167
+ if (itemFacetValues && itemFacetValues.length) {
168
+ if (group.match === 'all') {
169
+ return group.children.every(facet => itemFacetValues.includes(facet.uid));
170
+ }
171
+ return group.children.some(facet => itemFacetValues.includes(facet.uid));
172
+ }
173
+ return false;
174
+ });
175
+ });
176
+ }
177
+
178
+ function clearFilters() {
179
+ facets.value.forEach(group => {
180
+ if (group.children) {
181
+ group.children.forEach(child => child.selected = false);
182
+ }
183
+ });
184
+ }
185
+
186
+ function handleFacetChange({ groupUid, facetUid, selected }) {
187
+ const group = facets.value.find(g => g.uid === groupUid);
188
+ if (group) {
189
+ if (!group.multiple && selected) {
190
+ group.children.forEach(f => {
191
+ if (f.uid !== facetUid) {
192
+ f.selected = false;
193
+ }
194
+ });
195
+ }
196
+ const facet = group.children.find(f => f.uid === facetUid);
197
+ if (facet) {
198
+ facet.selected = selected;
199
+ }
200
+ }
201
+ }
202
+
203
+ // --- Watchers ---
204
+ watch(generatedFacets, (newGeneratedFacets) => {
205
+ facets.value = createFacets(initialFacets || newGeneratedFacets);
206
+ }, { immediate: true });
207
+
208
+ watch([selectedFacets, searchedItems], ([currentSelected, currentSearchedItems], [prevSelected, prevSearchedItems]) => {
209
+ if (countMode === 'none' || !facets.value.length) return;
210
+
211
+ // A simple optimization to prevent re-calculating counts if the actual data hasn't changed.
212
+ if (currentSelected === prevSelected && currentSearchedItems === prevSearchedItems) return;
213
+
214
+ if (countMode === 'simple') {
215
+ facets.value.forEach(group => {
216
+ const otherSelected = currentSelected.filter(g => g.uid !== group.uid);
217
+ const itemsToCount = getFilteredItems(currentSearchedItems, otherSelected);
218
+ group.children.forEach(child => {
219
+ child.count = itemsToCount.filter(item => {
220
+ const itemValues = getItemFacet(item, group.uid);
221
+ return itemValues.includes(child.uid);
222
+ }).length;
223
+ });
224
+ });
225
+ } else if (countMode === 'intuitive') {
226
+ facets.value.forEach(group => {
227
+ group.children.forEach(child => {
228
+ const tempFacets = JSON.parse(JSON.stringify(facets.value));
229
+ const tempGroup = tempFacets.find(g => g.uid === group.uid);
230
+ const tempChild = tempGroup.children.find(c => c.uid === child.uid);
231
+
232
+ if (tempGroup.multiple) {
233
+ if (tempChild.selected) {
234
+ child.count = filteredItems.value.filter(item => {
235
+ const itemValues = getItemFacet(item, group.uid);
236
+ return itemValues.includes(child.uid);
237
+ }).length;
238
+ return;
239
+ }
240
+ tempChild.selected = true;
241
+ } else {
242
+ if (tempChild.selected) {
243
+ child.count = filteredItems.value.length;
244
+ return;
245
+ }
246
+ tempGroup.children.forEach(f => { f.selected = false; });
247
+ tempChild.selected = true;
248
+ }
249
+
250
+ const tempSelected = tempFacets.map(g => ({...g, children: g.children.filter(c => c.selected)})).filter(g => g.children.length > 0);
251
+ const resultItems = getFilteredItems(currentSearchedItems, tempSelected);
252
+ child.count = resultItems.length;
253
+ });
254
+ });
255
+ }
256
+ }, { deep: true, immediate: true });
257
+
258
+ return {
259
+ facets,
260
+ searchValue,
261
+ selectedSort,
262
+ sortTypes,
263
+ displayItems,
264
+ selectedFacets,
265
+ clearFilters,
266
+ handleFacetChange
267
+ };
268
+ }
@@ -1,17 +1,28 @@
1
- export { default as UluFacets } from './facets/UluFacets.vue';
1
+ export { useFacets } from './facets/useFacets.js';
2
+ export { default as UluFacetsFilterLists } from './facets/UluFacetsFilterLists.vue';
3
+ export { default as UluFacetsFilterPopovers } from './facets/UluFacetsFilterPopovers.vue';
4
+ export { default as UluFacetsFilterSelects } from './facets/UluFacetsFilterSelects.vue';
5
+ export { default as UluFacetsHeaderLayout } from './facets/UluFacetsHeaderLayout.vue';
6
+ export { default as UluFacetsResults } from './facets/UluFacetsResults.vue';
2
7
  export { default as UluFacetsSearch } from './facets/UluFacetsSearch.vue';
8
+ export { default as UluFacetsSidebarLayout } from './facets/UluFacetsSidebarLayout.vue';
9
+ export { default as UluFacetsSort } from './facets/UluFacetsSort.vue';
3
10
  export { default as UluFacetsList } from './facets/UluFacetsList.vue';
11
+
4
12
  export { default as UluScrollAnchors } from './scroll-anchors/UluScrollAnchors.vue';
5
13
  export { default as UluScrollAnchorsNav } from './scroll-anchors/UluScrollAnchorsNav.vue';
6
14
  export { default as UluScrollAnchorsNavAnimated } from './scroll-anchors/UluScrollAnchorsNavAnimated.vue';
7
15
  export { default as UluScrollAnchorsSection } from './scroll-anchors/UluScrollAnchorsSection.vue';
16
+
8
17
  export { default as UluShowSkeleton } from './skeleton/UluShowSkeleton.vue';
9
18
  export { default as UluSkeletonContent } from './skeleton/UluSkeletonContent.vue';
10
19
  export { default as UluSkeletonMedia } from './skeleton/UluSkeletonMedia.vue';
11
- export { default as UluSkeletonTextInline } from './skeleton/UluSkeletonTextInline.vue';
20
+ export { default as UluSkeletonText } from './skeleton/UluSkeletonText.vue';
21
+
12
22
  export { default as UluImageSlideShow } from './slider/UluImageSlideShow.vue';
13
23
  export { default as UluSlideShow } from './slider/UluSlideShow.vue';
14
24
  export { default as UluSlideShowSlide } from './slider/UluSlideShowSlide.vue';
25
+
15
26
  export { default as UluTableSticky } from './table-sticky/UluTableSticky.vue';
16
27
  export { default as UluTableStickyRows } from './table-sticky/UluTableStickyRows.vue';
17
28
  export { default as UluTableStickyTable } from './table-sticky/UluTableStickyTable.vue';
@@ -10,6 +10,7 @@
10
10
  import { SECTIONS, REGISTER, UNREGISTER } from "./symbols.js";
11
11
  export default {
12
12
  name: "ScrollAnchors",
13
+ emits: ["section-change"],
13
14
  props: {
14
15
  firstItemActive: Boolean,
15
16
  /**
@@ -97,7 +98,7 @@
97
98
  } else if (lastExiting && section.active) {
98
99
  removeActive();
99
100
  }
100
- this.$emit("sectionChange", {
101
+ this.$emit("section-change", {
101
102
  section,
102
103
  sections,
103
104
  active: isIntersecting
@@ -1,13 +1,14 @@
1
1
  <template>
2
2
  <slot v-if="!when"/>
3
- <SkeletonTextInline class="skeleton" v-else/>
3
+ <UluSkeletonText v-else inline/>
4
4
  </template>
5
5
 
6
- <script>
7
- export default {
8
- name: "ShowSkeleton",
9
- props: {
10
- when: Boolean,
11
- },
12
- };
6
+ <script setup>
7
+ import UluSkeletonText from "./UluSkeletonText.vue";
8
+ defineProps({
9
+ /**
10
+ * If true will show whatever is passed to slot, else skeleton text
11
+ */
12
+ when: Boolean,
13
+ });
13
14
  </script>
@@ -1,11 +1,11 @@
1
1
  <template>
2
- <div class="skeleton">
2
+ <div>
3
3
  <div v-for="(line, index) in linesWithSegments" :key="index">
4
4
  <span
5
5
  v-for="segment in line"
6
6
  :key="segment"
7
- class="skeleton__text skeleton__text--inline"
8
- :class="{ 'skeleton__alt' : segment.alt }"
7
+ class="skeleton skeleton--text skeleton--inline"
8
+ :class="{ 'skeleton--background-alt' : segment.alt }"
9
9
  :style="{ width: `${ segment.width }%` }"
10
10
  >
11
11
  </span>
@@ -13,48 +13,44 @@
13
13
  </div>
14
14
  </template>
15
15
 
16
- <script>
16
+ <script setup>
17
+ import { computed } from "vue";
17
18
  import { randomInt } from "@ulu/utils/random.js";
18
19
  import { arrayCreate } from "@ulu/utils/array.js";
19
- export default {
20
- name: "SkeletonContent",
21
- props: {
22
- lines: {
23
- type: Number,
24
- default: 6
25
- },
26
- },
27
- methods: {
28
- randomInt
20
+
21
+ const props = defineProps({
22
+ /**
23
+ * Amount of lines to generate
24
+ */
25
+ lines: {
26
+ type: Number,
27
+ default: 6
29
28
  },
30
- computed: {
31
- /**
32
- * Creates the segments (like words) for the given line count
33
- * - Uses random number of segments and makes sure to fill the line between 70% - 100%
34
- */
35
- linesWithSegments() {
36
- return arrayCreate(this.lines, () => {
37
- const minWidth = 15;
38
- const total = randomInt(70, 100);
39
- let widthCurrent = 0;
40
- const newWidth = () => {
41
- const remaining = total - widthCurrent;
42
- const width = randomInt(minWidth, remaining);
43
- widthCurrent += width;
44
- return width;
45
- };
46
- const segments = [];
47
- while (widthCurrent < total - minWidth) {
48
- segments.push(newWidth());
49
- }
50
- const getActualTotal = () => segments.reduce((acc, a) => acc + a, 0);
51
- while (getActualTotal() >= total) {
52
- let removed = segments.pop();
53
- if (!removed) break;
54
- }
55
- return segments.map(width => ({ width, alt: Math.random() < 0.5 }));
56
- });
57
- }
29
+ });
30
+
31
+ /**
32
+ * Creates the segments (like words) for the given line count
33
+ * - Uses random number of segments and makes sure to fill the line between 70% - 100%
34
+ */
35
+ const linesWithSegments = computed(() => arrayCreate(props.lines, () => {
36
+ const minWidth = 15;
37
+ const total = randomInt(70, 100);
38
+ let widthCurrent = 0;
39
+ const newWidth = () => {
40
+ const remaining = total - widthCurrent;
41
+ const width = randomInt(minWidth, remaining);
42
+ widthCurrent += width;
43
+ return width;
44
+ };
45
+ const segments = [];
46
+ while (widthCurrent < total - minWidth) {
47
+ segments.push(newWidth());
48
+ }
49
+ const getActualTotal = () => segments.reduce((acc, a) => acc + a, 0);
50
+ while (getActualTotal() >= total) {
51
+ let removed = segments.pop();
52
+ if (!removed) break;
58
53
  }
59
- };
54
+ return segments.map(width => ({ width, alt: Math.random() < 0.5 }));
55
+ }));
60
56
  </script>
@@ -1,11 +1,9 @@
1
1
  <template>
2
- <div class="skeleton__block skeleton__block--media layout-flex-center-all">
3
- <FaIcon style="font-size: 2rem" icon="image"/>
2
+ <div class="skeleton skeleton-block--media">
3
+ <UluIcon icon="type:image"/>
4
4
  </div>
5
5
  </template>
6
6
 
7
- <script>
8
- export default {
9
- name: "SkeletonMedia",
10
- };
7
+ <script setup>
8
+ import UluIcon from "../../elements/UluIcon.vue";
11
9
  </script>
@@ -0,0 +1,27 @@
1
+ <template>
2
+ <span
3
+ class="skeleton skeleton--text"
4
+ :class="{
5
+ 'skeleton--inline' : inline,
6
+ 'skeleton--background-alt' : alt,
7
+ [`skeleton--width-${ width }`] : width
8
+ }"
9
+ ></span>
10
+ </template>
11
+
12
+ <script setup>
13
+ defineProps({
14
+ /**
15
+ * Inline modifier
16
+ */
17
+ inline: Boolean,
18
+ /**
19
+ * Use alternate background color
20
+ */
21
+ alt: Boolean,
22
+ /**
23
+ * Optional size (width) - should correspond with width setup in scss component
24
+ */
25
+ width: String
26
+ });
27
+ </script>
@@ -9,7 +9,7 @@
9
9
  <UluSlideShow
10
10
  class="slideshow--images"
11
11
  :items="images"
12
- @slideChange="slideChange"
12
+ @slide-change="slideChange"
13
13
  >
14
14
  <template #slide="{ item }">
15
15
  <img :src="item.src" :alt="item.alt">
@@ -50,7 +50,7 @@
50
50
  @click="previous"
51
51
  :disabled="!canScrollLeft"
52
52
  >
53
- <FaIcon class="slideshow__control-icon" icon="fas fa-chevron-left"/>
53
+ <UluIcon class="slideshow__control-icon" icon="type:next"/>
54
54
  </button>
55
55
  </li>
56
56
  <li class="slideshow__controls-item slideshow__controls-item--next">
@@ -60,7 +60,7 @@
60
60
  @click="next"
61
61
  :disabled="!canScrollRight"
62
62
  >
63
- <FaIcon class="slideshow__control-icon" icon="fas fa-chevron-right"/>
63
+ <UluIcon class="slideshow__control-icon" icon="type:previous" />
64
64
  </button>
65
65
  </li>
66
66
  </ul>
@@ -97,8 +97,13 @@
97
97
  </template>
98
98
 
99
99
  <script>
100
+ import UluIcon from "../../elements/UluIcon.vue";
100
101
  export default {
101
102
  name: 'SlideShow',
103
+ emits: ['slide-change'],
104
+ components: {
105
+ UluIcon
106
+ },
102
107
  props: {
103
108
  /**
104
109
  * Should slides be focusable by tab key
@@ -278,7 +283,7 @@
278
283
  this.$nextTick(() => {
279
284
  const slide = this.getSlideByElement(entry.target);
280
285
  slide.active = entry.isIntersecting;
281
- this.$emit('slideChange', { slide, track, nav });
286
+ this.$emit('slide-change', { slide, track, nav });
282
287
  });
283
288
  });
284
289
  };
@@ -43,7 +43,7 @@
43
43
  pointerEvents: sizesCalculated ? 'auto' : 'none',
44
44
  width: tableWidth
45
45
  }"
46
- @columnSorted="applySort"
46
+ @column-sorted="applySort"
47
47
  >
48
48
  <template v-for="(_, name) in $slots" v-slot:[name]="slotData">
49
49
  <slot :name="name" v-bind="slotData" />
@@ -66,7 +66,7 @@
66
66
  opacity: headerOpacityX,
67
67
  pointerEvents: headerVisibleX ? 'auto' : 'none'
68
68
  }"
69
- @columnSorted="applySort"
69
+ @column-sorted="applySort"
70
70
  >
71
71
  <template v-for="(_, name) in $slots" v-slot:[name]="slotData">
72
72
  <slot :name="name" v-bind="slotData" />
@@ -138,9 +138,9 @@
138
138
  :getRowValue="getRowValue"
139
139
  :getColumnTitle="getColumnTitle"
140
140
  @vue:mounted="tableReady"
141
- @actualHeaderRemoved="headerRemoved"
142
- @actualHeaderAdded="headerAdded"
143
- @columnSorted="applySort"
141
+ @actual-header-removed="headerRemoved"
142
+ @actual-header-added="headerAdded"
143
+ @column-sorted="applySort"
144
144
  >
145
145
  <template v-for="(_, name) in $slots" v-slot:[name]="slotData">
146
146
  <slot :name="name" v-bind="slotData" />
@@ -167,7 +167,7 @@
167
167
  opacity: headerOpacityX,
168
168
  pointerEvents: headerVisibleX ? 'auto' : 'none'
169
169
  }"
170
- @columnSorted="applySort"
170
+ @column-sorted="applySort"
171
171
  >
172
172
  <template v-for="(_, name) in $slots" v-slot:[name]="slotData">
173
173
  <slot :name="name" v-bind="slotData" />
@@ -443,7 +443,7 @@
443
443
  } else {
444
444
  column.sortApplied = true;
445
445
  }
446
- this.$emit("columnSort", column);
446
+ this.$emit("column-sort", column);
447
447
  },
448
448
  onColumnResize() {
449
449
  if (this.sizesPainted) {
@@ -50,7 +50,7 @@
50
50
  :class="{
51
51
  'table-sticky__sort-button--focused' : column.sortFocused,
52
52
  }"
53
- @click="$emit('columnSorted', column)"
53
+ @click="$emit('column-sorted', column)"
54
54
  @focus="handleSortFocus(column, true)"
55
55
  @blur="handleSortFocus(column, false)"
56
56
  :aria-pressed="column.sortApplied ? 'true' : 'false'"
@@ -199,9 +199,9 @@
199
199
  const { id } = column;
200
200
  const old = headerRefs[id];
201
201
  if (old) {
202
- this.$emit("actualHeaderRemoved", old);
202
+ this.$emit("actual-header-removed", old);
203
203
  }
204
- this.$emit("actualHeaderAdded", el);
204
+ this.$emit("actual-header-added", el);
205
205
  headerRefs[id] = el;
206
206
  },
207
207
  /**
@@ -4,11 +4,17 @@
4
4
 
5
5
  <script>
6
6
  import gsap from "gsap";
7
+
8
+ /**
9
+ * Animates a number from a previous value to a new value.
10
+ * @slot default - The default slot for customizing the display of the number.
11
+ * @binding {number} currentValue - The current animated value.
12
+ */
7
13
  export default {
8
14
  name: 'AnimateNumber',
9
15
  props: {
10
16
  /**
11
- * Number to animate as it changes
17
+ * The target number to animate to.
12
18
  */
13
19
  value: Number
14
20
  },