@asd20/ui-next 2.0.24 → 2.0.26

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.0.26](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.0.25...ui-next-v2.0.26) (2026-04-03)
4
+
5
+ ## [2.0.25](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.0.24...ui-next-v2.0.25) (2026-04-03)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * allow article list and digest pages to process more than one category selection ([5ebf88a](https://github.com/academydistrict20/asd20-ui-next/commit/5ebf88a9d51d44de3976df3951ccf925cec1f269))
11
+
3
12
  ## [2.0.24](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.0.23...ui-next-v2.0.24) (2026-04-03)
4
13
 
5
14
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asd20/ui-next",
3
- "version": "2.0.24",
3
+ "version": "2.0.26",
4
4
  "private": false,
5
5
  "description": "ASD20 UI component library for Vue 3.",
6
6
  "license": "MIT",
@@ -291,6 +291,7 @@ $input-reversed-text-color: var(--color__accent-t100);
291
291
  & :deep(.multiselect__input:focus),
292
292
  & :deep(.multiselect__single:focus) {
293
293
  border-color: var(--color__accent);
294
+ border-radius: 0.2rem;
294
295
  @include asd20-focus;
295
296
  }
296
297
  & :deep(.multiselect__tags) {
@@ -308,7 +309,8 @@ $input-reversed-text-color: var(--color__accent-t100);
308
309
  color: var(--website-page__background-color);
309
310
  font-weight: normal;
310
311
  background: var(--background-color);
311
- border-radius: 0;
312
+ border-radius: 0.25rem;
313
+ padding-left: 0.25rem;
312
314
  }
313
315
  & :deep(.multiselect__tag-icon) {
314
316
  font-weight: bold;
@@ -368,6 +370,15 @@ $input-reversed-text-color: var(--color__accent-t100);
368
370
  background: var(--color__accent);
369
371
  color: var(--website-page__background-color);
370
372
  }
373
+ & :deep(.multiselect__option--highlight:after),
374
+ & :deep(.multiselect__option--selected:after),
375
+ & :deep(.multiselect__option--selected.multiselect__option--highlight:after) {
376
+ display: block;
377
+ margin-top: 0.25rem;
378
+ font-size: 0.75rem;
379
+ font-weight: 400;
380
+ line-height: 1.2;
381
+ }
371
382
  & :deep(.multiselect__option),
372
383
  & :deep(.option__title) {
373
384
  color: var(--website-page__title-color);
@@ -90,7 +90,7 @@
90
90
  :items="categoryOptions"
91
91
  :hide-label="true"
92
92
  placeholder="Filter by Category"
93
- @update:modelValue="$emit('update:selected-categories', $event)"
93
+ @update:modelValue="onCategorySelect"
94
94
  />
95
95
  </div>
96
96
 
@@ -282,6 +282,7 @@ import Intersect from '../../../components/utils/Intersect'
282
282
 
283
283
  // Mixins
284
284
  import pageTemplateMixin from '../../../mixins/pageTemplateMixin'
285
+ import categoryFilterQuerySyncMixin from '../../../mixins/categoryFilterQuerySyncMixin'
285
286
 
286
287
  // Helpers
287
288
  import mapMessageToCard from '../../../helpers/mapMessageToCard'
@@ -304,7 +305,7 @@ export default {
304
305
  Intersect,
305
306
  Asd20MediaSection,
306
307
  },
307
- mixins: [pageTemplateMixin],
308
+ mixins: [pageTemplateMixin, categoryFilterQuerySyncMixin],
308
309
 
309
310
  props: {
310
311
  keywords: { type: String, default: '' },
@@ -299,6 +299,7 @@ import Intersect from '../../../components/utils/Intersect'
299
299
 
300
300
  // Mixins
301
301
  import pageTemplateMixin from '../../../mixins/pageTemplateMixin'
302
+ import categoryFilterQuerySyncMixin from '../../../mixins/categoryFilterQuerySyncMixin'
302
303
 
303
304
  // Helpers
304
305
  import mapMessageToCard from '../../../helpers/mapMessageToCard'
@@ -322,7 +323,7 @@ export default {
322
323
  Intersect,
323
324
  Asd20MediaSection,
324
325
  },
325
- mixins: [pageTemplateMixin],
326
+ mixins: [pageTemplateMixin, categoryFilterQuerySyncMixin],
326
327
  props: {
327
328
  keywords: { type: String, default: '' },
328
329
  selectedCategories: { type: Array, default: () => [] },
@@ -332,7 +333,6 @@ export default {
332
333
  numberToShow: 25,
333
334
  counter: 1,
334
335
  counter2: 25,
335
- currentCategory: null,
336
336
  }
337
337
  },
338
338
  computed: {
@@ -367,57 +367,8 @@ export default {
367
367
  this.reset() // Reset page data when categories change
368
368
  }
369
369
  },
370
- categoryOptions(newOptions) {
371
- // Ensure $route and $route.query are defined before accessing query.category
372
- if (this.$route && this.$route.query) {
373
- const categoryFromQuery = this.$route.query.category
374
- if (categoryFromQuery && newOptions.length) {
375
- this.setCategoryFromUrl(categoryFromQuery) // Set category from URL when options are ready
376
- }
377
- }
378
- },
379
370
  },
380
371
  methods: {
381
- setCategoryFromUrl(category) {
382
- // Avoid re-processing if the category is already set
383
- if (this.currentCategory !== category) {
384
- const matchingCategory = this.categoryOptions.find(
385
- opt => opt === category
386
- )
387
- if (matchingCategory) {
388
- this.currentCategory = category // Update the current category
389
- this.$emit('update:selected-categories', [
390
- { name: matchingCategory, id: matchingCategory },
391
- ])
392
- }
393
- }
394
- },
395
- onCategorySelect(selected) {
396
- // Ensure $router and $route are defined before using them
397
- if (this.$router && this.$route) {
398
- if (selected.length > 0) {
399
- const selectedCategory = selected[0].id
400
-
401
- // Only update if the selection has changed
402
- if (this.currentCategory !== selectedCategory) {
403
- this.currentCategory = selectedCategory // Track the selected category
404
- this.$emit('update:selected-categories', selected) // Update selected categories
405
- this.$router.replace({
406
- query: { ...this.$route.query, category: selectedCategory },
407
- })
408
- }
409
- } else {
410
- // Handle clearing the selection
411
- this.currentCategory = null // Reset current category
412
- this.$emit('update:selected-categories', []) // Clear selected categories
413
-
414
- // Remove the 'category' parameter entirely from the URL
415
- const { ...remainingQuery } = this.$route.query || {} // Ensure $route.query is defined
416
- delete remainingQuery.category
417
- this.$router.replace({ query: remainingQuery }) // Update the URL without the category
418
- }
419
- }
420
- },
421
372
  nextSet() {
422
373
  if (
423
374
  Math.ceil(this.counter2 / this.numberToShow) <
@@ -33,6 +33,7 @@
33
33
  </div>
34
34
 
35
35
  <input
36
+ v-show="showSearchInput"
36
37
  :id="id"
37
38
  ref="searchInput"
38
39
  :value="search"
@@ -203,6 +204,9 @@ export default {
203
204
  showPlaceholder() {
204
205
  return !this.search && !this.selectedOptions.length && !this.isOpen
205
206
  },
207
+ showSearchInput() {
208
+ return this.isOpen || !!this.search
209
+ },
206
210
  placeholderText() {
207
211
  return this.placeholder || 'Select options'
208
212
  },
@@ -479,7 +483,7 @@ export default {
479
483
  width: auto;
480
484
  min-height: 1.25rem;
481
485
  margin: 0;
482
- padding: 0;
486
+ padding: 0 0 0 0.25rem;
483
487
  border: 0;
484
488
  outline: 0;
485
489
  background: transparent;
@@ -0,0 +1,170 @@
1
+ function asArray(value) {
2
+ if (Array.isArray(value)) return value
3
+ if (value === null || value === undefined || value === '') return []
4
+ return [value]
5
+ }
6
+
7
+ function uniqueValues(values) {
8
+ return [...new Set(values)]
9
+ }
10
+
11
+ export default {
12
+ mounted() {
13
+ this.syncCategoriesFromRoute()
14
+ },
15
+ watch: {
16
+ categoryOptions(newOptions) {
17
+ if (Array.isArray(newOptions) && newOptions.length) {
18
+ this.syncCategoriesFromRoute()
19
+ }
20
+ },
21
+ },
22
+ methods: {
23
+ normalizeCategoryQueryValues(value) {
24
+ return uniqueValues(
25
+ asArray(value)
26
+ .flatMap(entry => String(entry).split(','))
27
+ .map(entry => entry.trim())
28
+ .filter(Boolean)
29
+ )
30
+ },
31
+ normalizeCategorySelection(selection) {
32
+ return asArray(selection)
33
+ .map(item => {
34
+ const id =
35
+ item && typeof item === 'object'
36
+ ? item.id ?? item.name ?? item.title
37
+ : item
38
+
39
+ if (id === null || id === undefined || id === '') {
40
+ return null
41
+ }
42
+
43
+ const label =
44
+ item && typeof item === 'object'
45
+ ? item.name ?? item.title ?? id
46
+ : id
47
+
48
+ return {
49
+ id,
50
+ name: label,
51
+ }
52
+ })
53
+ .filter(Boolean)
54
+ },
55
+ getCategoryQueryValues(query = this.$route && this.$route.query) {
56
+ if (!query) return []
57
+
58
+ if (Object.prototype.hasOwnProperty.call(query, 'categories')) {
59
+ return this.normalizeCategoryQueryValues(query.categories)
60
+ }
61
+
62
+ if (Object.prototype.hasOwnProperty.call(query, 'category')) {
63
+ return this.normalizeCategoryQueryValues(query.category)
64
+ }
65
+
66
+ return []
67
+ },
68
+ buildSelectionFromCategoryQuery() {
69
+ const queryValues = this.getCategoryQueryValues()
70
+
71
+ if (!queryValues.length || !Array.isArray(this.categoryOptions)) {
72
+ return []
73
+ }
74
+
75
+ const optionLookup = new Map(
76
+ this.categoryOptions.map(option => [String(option), String(option)])
77
+ )
78
+
79
+ return queryValues
80
+ .filter(value => optionLookup.has(String(value)))
81
+ .map(value => {
82
+ const option = optionLookup.get(String(value))
83
+ return {
84
+ id: option,
85
+ name: option,
86
+ }
87
+ })
88
+ },
89
+ hasSameCategorySelection(left, right) {
90
+ const leftIds = this.normalizeCategorySelection(left).map(item => String(item.id))
91
+ const rightIds = this.normalizeCategorySelection(right).map(item => String(item.id))
92
+
93
+ return (
94
+ leftIds.length === rightIds.length &&
95
+ leftIds.every((id, index) => id === rightIds[index])
96
+ )
97
+ },
98
+ syncCategoriesFromRoute() {
99
+ const routeSelection = this.buildSelectionFromCategoryQuery()
100
+
101
+ if (!routeSelection.length) return
102
+ if (this.hasSameCategorySelection(routeSelection, this.selectedCategories)) {
103
+ return
104
+ }
105
+
106
+ this.$emit('update:selected-categories', routeSelection)
107
+ },
108
+ categoryQueryMatchesSelection(selection) {
109
+ const normalizedSelection = this.normalizeCategorySelection(selection)
110
+ const selectionIds = normalizedSelection.map(item => String(item.id))
111
+ const query = (this.$route && this.$route.query) || {}
112
+ const queryIds = this.getCategoryQueryValues(query)
113
+
114
+ if (
115
+ selectionIds.length !== queryIds.length ||
116
+ selectionIds.some((id, index) => id !== queryIds[index])
117
+ ) {
118
+ return false
119
+ }
120
+
121
+ const hasCategory = Object.prototype.hasOwnProperty.call(query, 'category')
122
+ const hasCategories = Object.prototype.hasOwnProperty.call(query, 'categories')
123
+
124
+ if (!selectionIds.length) {
125
+ return !hasCategory && !hasCategories
126
+ }
127
+
128
+ if (String(query.category) !== selectionIds[0]) {
129
+ return false
130
+ }
131
+
132
+ if (selectionIds.length === 1) {
133
+ return !hasCategories
134
+ }
135
+
136
+ const categoryQueryValues = this.normalizeCategoryQueryValues(query.categories)
137
+
138
+ return (
139
+ categoryQueryValues.length === selectionIds.length &&
140
+ categoryQueryValues.every((id, index) => id === selectionIds[index])
141
+ )
142
+ },
143
+ updateCategoryQuery(selection) {
144
+ if (!this.$router || !this.$route) return
145
+ if (this.categoryQueryMatchesSelection(selection)) return
146
+
147
+ const normalizedSelection = this.normalizeCategorySelection(selection)
148
+ const nextQuery = { ...(this.$route.query || {}) }
149
+ const selectionIds = normalizedSelection.map(item => item.id)
150
+
151
+ delete nextQuery.category
152
+ delete nextQuery.categories
153
+
154
+ if (selectionIds.length === 1) {
155
+ nextQuery.category = selectionIds[0]
156
+ } else if (selectionIds.length > 1) {
157
+ nextQuery.category = selectionIds[0]
158
+ nextQuery.categories = selectionIds
159
+ }
160
+
161
+ this.$router.replace({ query: nextQuery })
162
+ },
163
+ onCategorySelect(selection) {
164
+ const normalizedSelection = this.normalizeCategorySelection(selection)
165
+
166
+ this.$emit('update:selected-categories', normalizedSelection)
167
+ this.updateCategoryQuery(normalizedSelection)
168
+ },
169
+ },
170
+ }