@asd20/ui-next 1.0.9 → 1.0.11

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 (49) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/package.json +1 -1
  3. package/src/components/atoms/Asd20Button/index.vue +1 -1
  4. package/src/components/atoms/Asd20CalendarEventButton/index.vue +1 -1
  5. package/src/components/molecules/Asd20ActionMenuItem/index.vue +1 -1
  6. package/src/components/molecules/Asd20Card/index.vue +1 -1
  7. package/src/components/molecules/Asd20ListItem/index.vue +1 -1
  8. package/src/components/molecules/Asd20SearchField/index.vue +1 -1
  9. package/src/components/molecules/Asd20SliderInput/index.vue +1 -1
  10. package/src/components/molecules/Asd20Swipe/index.vue +1 -1
  11. package/src/components/molecules/Asd20Tab/index.vue +1 -1
  12. package/src/components/organisms/Asd20AccessibilityIssueModal/index.vue +6 -6
  13. package/src/components/organisms/Asd20ActionMenu/index.vue +0 -12
  14. package/src/components/organisms/Asd20AiSearch/index.vue +18 -0
  15. package/src/components/organisms/Asd20CampusHeader/index.vue +1 -1
  16. package/src/components/organisms/Asd20EventModal/index.vue +3 -3
  17. package/src/components/organisms/Asd20ImageHeader/index.vue +2 -2
  18. package/src/components/organisms/Asd20PageFooter/index.vue +0 -22
  19. package/src/components/organisms/Asd20PageHeader/index.vue +2 -2
  20. package/src/components/organisms/Asd20SiteSearch/index.vue +122 -77
  21. package/src/components/templates/Asd20AppTemplate/index.vue +1 -1
  22. package/src/components/templates/Asd20ArticleDigestCompactTemplate/index.vue +1 -1
  23. package/src/components/templates/Asd20ArticleDigestTemplate/index.vue +1 -1
  24. package/src/components/templates/Asd20ArticleListTemplate/index.vue +1 -1
  25. package/src/components/templates/Asd20ArticleTemplate/index.vue +1 -1
  26. package/src/components/templates/Asd20BasePageTemplate/index.vue +1 -1
  27. package/src/components/templates/Asd20CampusDetailTemplate/index.vue +1 -1
  28. package/src/components/templates/Asd20CampusTemplate/index.vue +1 -1
  29. package/src/components/templates/Asd20ClubsTemplate/index.vue +1 -1
  30. package/src/components/templates/Asd20DetailAlternateTemplate/index.vue +1 -1
  31. package/src/components/templates/Asd20DetailImageFullTemplate/index.vue +1 -1
  32. package/src/components/templates/Asd20DetailImageTemplate/index.vue +1 -1
  33. package/src/components/templates/Asd20DetailTemplate/index.vue +1 -1
  34. package/src/components/templates/Asd20DistrictHomeTemplate/index.vue +1 -1
  35. package/src/components/templates/Asd20DistrictVideoTemplate/index.vue +21 -1
  36. package/src/components/templates/Asd20GroupFeatureTemplate/index.vue +1 -1
  37. package/src/components/templates/Asd20LoginsTemplate/index.vue +1 -1
  38. package/src/components/templates/Asd20PeopleFeatureTemplate/index.vue +1 -1
  39. package/src/components/templates/Asd20ProfileTemplate/index.vue +1 -1
  40. package/src/components/templates/Asd20SalaryCalculatorTemplate/index.vue +1 -1
  41. package/src/components/templates/Asd20SchoolHomeTemplate/index.vue +21 -1
  42. package/src/components/templates/Asd20SchoolHomeVideoTemplate/index.vue +21 -1
  43. package/src/components/templates/Asd20SearchAppTemplate/index.vue +1 -1
  44. package/src/components/templates/Asd20WayfindingAccessibilityTemplate/index.vue +1 -1
  45. package/src/components/templates/Asd20WayfindingAlternateTemplate/index.vue +1 -1
  46. package/src/components/templates/Asd20WayfindingImageTemplate/index.vue +1 -1
  47. package/src/components/templates/Asd20WayfindingTemplate/index.vue +1 -1
  48. package/src/mixins/globalPropMixinFactory.js +46 -3
  49. package/src/mixins/hasLoaderMixin.js +14 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.11](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v1.0.10...ui-next-v1.0.11) (2026-03-26)
4
+
5
+ ## [1.0.10](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v1.0.9...ui-next-v1.0.10) (2026-03-26)
6
+
3
7
  ## [1.0.9](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v1.0.8...ui-next-v1.0.9) (2026-03-23)
4
8
 
5
9
  ## [1.0.8](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v1.0.7...ui-next-v1.0.8) (2026-03-23)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asd20/ui-next",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "private": false,
5
5
  "description": "ASD20 UI component library migration workspace.",
6
6
  "license": "MIT",
@@ -29,7 +29,6 @@ import { shouldOpenInNewWindow } from '../../../helpers/linkPolicy'
29
29
 
30
30
  export default {
31
31
  name: 'Asd20Button',
32
- emits: ['click', 'mouseup', 'keyup', 'focusin'],
33
32
  components: { Asd20Badge, Asd20Icon },
34
33
  props: {
35
34
  label: { type: String, default: '' },
@@ -54,6 +53,7 @@ export default {
54
53
  disabled: { type: Boolean, default: false },
55
54
  iconAngle: { type: Number, default: 0 },
56
55
  },
56
+ emits: ['click', 'mouseup', 'keyup', 'focusin'],
57
57
  computed: {
58
58
  classes() {
59
59
  return {
@@ -49,11 +49,11 @@ import { format } from 'date-fns'
49
49
 
50
50
  export default {
51
51
  name: 'Asd20CalendarEventButton',
52
- emits: ['click'],
53
52
  props: {
54
53
  event: { type: Object, default: null },
55
54
  large: { type: Boolean, default: false },
56
55
  },
56
+ emits: ['click'],
57
57
  computed: {
58
58
  classes() {
59
59
  const event = this.event || {}
@@ -18,7 +18,6 @@
18
18
  import Asd20Button from '../../atoms/Asd20Button'
19
19
  export default {
20
20
  name: 'Asd20ActionMenuItem',
21
- emits: ['click', 'mouseup', 'keyup'],
22
21
  components: {
23
22
  Asd20Button,
24
23
  },
@@ -38,6 +37,7 @@ export default {
38
37
  active: { type: Boolean, default: false },
39
38
  // target: { type: [Boolean, String], default: false },
40
39
  },
40
+ emits: ['click', 'mouseup', 'keyup'],
41
41
  computed: {
42
42
  classes() {
43
43
  return [
@@ -260,7 +260,6 @@ import truncate from 'lodash/truncate'
260
260
 
261
261
  export default {
262
262
  name: 'Asd20Card',
263
- emits: ['click', 'keydown'],
264
263
 
265
264
  components: { Asd20Icon, Asd20Tag },
266
265
 
@@ -306,6 +305,7 @@ export default {
306
305
  dateLeft: { type: Boolean, default: false },
307
306
  cardType: { type: String, default: '' },
308
307
  },
308
+ emits: ['click', 'keydown'],
309
309
  computed: {
310
310
  truncatedDescription() {
311
311
  const strippedDescription = this.description
@@ -120,7 +120,6 @@ import {
120
120
 
121
121
  export default {
122
122
  name: 'Asd20ListItem',
123
- emits: ['click'],
124
123
 
125
124
  components: {
126
125
  Asd20Icon,
@@ -152,6 +151,7 @@ export default {
152
151
  logoUrl: { type: String, default: '' },
153
152
  attribution: { type: Boolean, default: false },
154
153
  },
154
+ emits: ['click'],
155
155
 
156
156
  computed: {
157
157
  classes() {
@@ -41,7 +41,6 @@ import Asd20Icon from '../../atoms/Asd20Icon'
41
41
 
42
42
  export default {
43
43
  name: 'Asd20SearchField',
44
- emits: ['input', 'update:modelValue', 'click', 'focusin', 'keyup'],
45
44
  components: { Asd20Icon },
46
45
  props: {
47
46
  value: { type: String, default: '' },
@@ -51,6 +50,7 @@ export default {
51
50
  large: { type: Boolean, default: false },
52
51
  placeholder: { type: String, default: 'Search' },
53
52
  },
53
+ emits: ['input', 'update:modelValue', 'click', 'focusin', 'keyup'],
54
54
  computed: {
55
55
  classes() {
56
56
  return {
@@ -24,7 +24,6 @@
24
24
  <script>
25
25
  export default {
26
26
  name: 'Asd20SliderInput',
27
- emits: ['input', 'update:modelValue'],
28
27
 
29
28
  props: {
30
29
  label: { type: String, default: '' },
@@ -35,6 +34,7 @@ export default {
35
34
  max: { type: Number, default: 100 },
36
35
  step: { type: Number, default: 1 },
37
36
  },
37
+ emits: ['input', 'update:modelValue'],
38
38
 
39
39
  computed: {
40
40
  resolvedValue() {
@@ -69,7 +69,6 @@ import createLegacyDestroyHooks from '../../../utils/createLegacyDestroyHooks'
69
69
 
70
70
  export default {
71
71
  name: 'Asd20Swipe',
72
- emits: ['change', 'click', 'keyup'],
73
72
  props: {
74
73
  loop: {
75
74
  type: Boolean,
@@ -104,6 +103,7 @@ export default {
104
103
  default: () => [],
105
104
  },
106
105
  },
106
+ emits: ['change', 'click', 'keyup'],
107
107
  data() {
108
108
  return {
109
109
  width: 0,
@@ -34,7 +34,6 @@ import Asd20Badge from '../../../components/atoms/Asd20Badge'
34
34
 
35
35
  export default {
36
36
  name: 'Asd20Tab',
37
- emits: ['click'],
38
37
 
39
38
  components: {
40
39
  Asd20Badge,
@@ -49,6 +48,7 @@ export default {
49
48
  active: { type: Boolean, default: false },
50
49
  to: { type: [String, Object], default: '' },
51
50
  },
51
+ emits: ['click'],
52
52
 
53
53
  computed: {
54
54
  component() {
@@ -71,12 +71,12 @@
71
71
  />
72
72
  <asd20-button
73
73
  label="Submit Issue"
74
- :disabled="!isValid || sending"
75
- horizontal
76
- centered
77
- bordered
78
- @click="sendAccessibilityIssue"
79
- />
74
+ :disabled="!isValid || sending"
75
+ horizontal
76
+ centered
77
+ bordered
78
+ @click="sendAccessibilityIssue"
79
+ />
80
80
  <asd20-spinner
81
81
  v-if="sending"
82
82
  size="sm"
@@ -23,18 +23,6 @@ export default {
23
23
  zoomed: { type: Boolean, default: false },
24
24
  emptyNav: { type: Boolean, default: false },
25
25
  },
26
-
27
- computed: {
28
- hidden() {
29
- return (this.$store && this.$store.state && this.$store.state.scrolling) || false
30
- },
31
- classes() {
32
- return {
33
- 'action-menu__fab--hidden': this.hidden,
34
- 'action-menu__fab--open': this.open,
35
- }
36
- },
37
- },
38
26
  }
39
27
  </script>
40
28
 
@@ -79,6 +79,15 @@
79
79
  :active="expanded"
80
80
  :inline="true"
81
81
  :show-contacts="false"
82
+ :pages="pages"
83
+ :files="files"
84
+ :groups="groups"
85
+ :include-district-results="includeDistrictResults"
86
+ :search-language-code="searchLanguageCode"
87
+ :query-pages-handler="queryPagesHandler"
88
+ :query-files-handler="queryFilesHandler"
89
+ :query-groups-handler="queryGroupsHandler"
90
+ :query-ai-site-handler="queryAiSiteHandler"
82
91
  :organization="organization"
83
92
  :organization-options="organizationOptions"
84
93
  :ai-search-feedback-form-url="aiSearchFeedbackFormUrl"
@@ -110,6 +119,15 @@ export default {
110
119
  Asd20Button,
111
120
  },
112
121
  props: {
122
+ pages: { type: Array, default: () => [] },
123
+ files: { type: Array, default: () => [] },
124
+ groups: { type: Array, default: () => [] },
125
+ includeDistrictResults: { type: Boolean, default: true },
126
+ searchLanguageCode: { type: String, default: null },
127
+ queryPagesHandler: { type: Function, default: null },
128
+ queryFilesHandler: { type: Function, default: null },
129
+ queryGroupsHandler: { type: Function, default: null },
130
+ queryAiSiteHandler: { type: Function, default: null },
113
131
  organization: { type: Object, default: () => ({}) },
114
132
  organizationOptions: { type: Array, default: () => [] },
115
133
  aiSearchFeedbackFormUrl: { type: String, default: '' },
@@ -90,13 +90,13 @@ import organizationSubtitle from '../../../helpers/organizationSubtitle'
90
90
 
91
91
  export default {
92
92
  name: 'Asd20CampusHeader',
93
- mixins: [responsiveBreakpointMixin],
94
93
  components: {
95
94
  Asd20Button,
96
95
  Asd20DistrictLogo,
97
96
  Asd20OrganizationPicker,
98
97
  },
99
98
  directives: { scrollTrack },
99
+ mixins: [responsiveBreakpointMixin],
100
100
  props: {
101
101
  organization: { type: Object, default: null },
102
102
  organizationOptions: { type: Array, default: () => [] },
@@ -221,9 +221,9 @@ export default {
221
221
  padding: space(1);
222
222
  font-size: 1rem;
223
223
 
224
- * {
225
- align-items: center;
226
- }
224
+ // * {
225
+ // align-items: center;
226
+ // }
227
227
  }
228
228
 
229
229
  .description {
@@ -48,7 +48,7 @@
48
48
  v-if="languageCode"
49
49
  reversed
50
50
  :language-code="languageCode"
51
- v-on="$listeners"
51
+ @update:languageCode="$emit('update:languageCode', $event)"
52
52
  />
53
53
  <Asd20Share></Asd20Share>
54
54
  <asd20-button
@@ -199,7 +199,6 @@ import truncate from 'lodash/truncate'
199
199
 
200
200
  export default {
201
201
  name: 'Asd20ImageHeader',
202
- mixins: [responsiveBreakpointMixin],
203
202
  components: {
204
203
  Asd20Button,
205
204
  // Asd20DistrictLogo,
@@ -211,6 +210,7 @@ export default {
211
210
  Asd20FormattedDate,
212
211
  },
213
212
  directives: { scrollTrack },
213
+ mixins: [responsiveBreakpointMixin],
214
214
  props: {
215
215
  heading: { type: String, default: '' },
216
216
  lead: { type: String, default: '' },
@@ -199,28 +199,6 @@ export default {
199
199
  websiteLogoProps2: { type: Object, default: null },
200
200
  },
201
201
  computed: {
202
- collapsed() {
203
- if (typeof window === 'undefined') return false
204
- const storeState = this.$store && this.$store.state
205
- if (!storeState) return false
206
- return (
207
- storeState.scrollPosition > window.innerHeight &&
208
- storeState.scrollVerticalDirection !== -1
209
- )
210
- },
211
- condensed() {
212
- if (typeof window === 'undefined') return false
213
- const storeState = this.$store && this.$store.state
214
- if (!storeState) return false
215
- return storeState.scrollPosition > window.innerHeight / 4
216
- },
217
- classes() {
218
- return {
219
- 'page-header--fullscreen': this.fullscreen,
220
- 'page-header--collapsed': this.collapsed,
221
- 'page-header--condensed': this.condensed,
222
- }
223
- },
224
202
  administrator() {
225
203
  if (
226
204
  !this.organization ||
@@ -47,7 +47,7 @@
47
47
  v-if="languageCode"
48
48
  reversed
49
49
  :language-code="languageCode"
50
- v-on="$listeners"
50
+ @update:languageCode="$emit('update:languageCode', $event)"
51
51
  />
52
52
  <Asd20Share></Asd20Share>
53
53
  <asd20-button
@@ -191,7 +191,6 @@ import truncate from 'lodash/truncate'
191
191
 
192
192
  export default {
193
193
  name: 'Asd20PageHeader',
194
- mixins: [responsiveBreakpointMixin],
195
194
  components: {
196
195
  Asd20Button,
197
196
  // Asd20DistrictLogo,
@@ -203,6 +202,7 @@ export default {
203
202
  Asd20FormattedDate,
204
203
  },
205
204
  directives: { scrollTrack },
205
+ mixins: [responsiveBreakpointMixin],
206
206
  props: {
207
207
  heading: { type: String, default: '' },
208
208
  lead: { type: String, default: '' },
@@ -677,6 +677,11 @@ export default {
677
677
  inline: { type: Boolean, default: false },
678
678
  showTabBar: { type: Boolean, default: true },
679
679
  showContacts: { type: Boolean, default: true },
680
+ searchLanguageCode: { type: String, default: null },
681
+ queryPagesHandler: { type: Function, default: null },
682
+ queryFilesHandler: { type: Function, default: null },
683
+ queryGroupsHandler: { type: Function, default: null },
684
+ queryAiSiteHandler: { type: Function, default: null },
680
685
  organization: {
681
686
  type: Object,
682
687
  default: () => ({ title: 'Academy District 20' }),
@@ -1218,7 +1223,61 @@ export default {
1218
1223
  : null
1219
1224
  },
1220
1225
 
1226
+ getSearchLanguageCode() {
1227
+ if (
1228
+ typeof this.searchLanguageCode === 'string' &&
1229
+ this.searchLanguageCode.trim()
1230
+ ) {
1231
+ return this.searchLanguageCode.trim()
1232
+ }
1233
+
1234
+ const searchStore = this.getSearchStore()
1235
+ const storeLanguageCode =
1236
+ searchStore &&
1237
+ searchStore.state &&
1238
+ searchStore.state.search &&
1239
+ searchStore.state.search.languageCode
1240
+
1241
+ return typeof storeLanguageCode === 'string' && storeLanguageCode.trim()
1242
+ ? storeLanguageCode.trim()
1243
+ : 'en'
1244
+ },
1245
+
1246
+ setSearchResults({ pages, files, groups } = {}) {
1247
+ if (pages !== undefined) this.resolvedPages = pages
1248
+ if (files !== undefined) this.resolvedFiles = files
1249
+ if (groups !== undefined) this.resolvedGroups = groups
1250
+ },
1251
+
1252
+ async querySearchCollectionWithFallback({
1253
+ handler,
1254
+ storeAction,
1255
+ payload,
1256
+ target,
1257
+ }) {
1258
+ if (typeof handler === 'function') {
1259
+ const result = await handler(payload)
1260
+ if (Array.isArray(result)) {
1261
+ this[target] = result
1262
+ return result
1263
+ }
1264
+ return Array.isArray(this[target]) ? this[target] : []
1265
+ }
1266
+
1267
+ const searchStore = this.getSearchStore()
1268
+ if (searchStore) {
1269
+ await searchStore.dispatch(storeAction, payload)
1270
+ return Array.isArray(this[target]) ? this[target] : []
1271
+ }
1272
+
1273
+ return Array.isArray(this[target]) ? this[target] : []
1274
+ },
1275
+
1221
1276
  async queryAiSiteWithFallback(payload) {
1277
+ if (typeof this.queryAiSiteHandler === 'function') {
1278
+ return this.queryAiSiteHandler(payload)
1279
+ }
1280
+
1222
1281
  const searchStore = this.getSearchStore()
1223
1282
  if (searchStore) {
1224
1283
  return searchStore.dispatch('search/queryAiSite', payload)
@@ -1565,15 +1624,7 @@ export default {
1565
1624
  const pages = Array.isArray(snapshot.pages) ? snapshot.pages : []
1566
1625
  const files = Array.isArray(snapshot.files) ? snapshot.files : []
1567
1626
  const groups = Array.isArray(snapshot.groups) ? snapshot.groups : []
1568
-
1569
- const searchStore = this.getSearchStore()
1570
- if (searchStore) {
1571
- await Promise.all([
1572
- searchStore.dispatch('search/setPages', pages),
1573
- searchStore.dispatch('search/setFiles', files),
1574
- searchStore.dispatch('search/setGroups', groups),
1575
- ])
1576
- }
1627
+ this.setSearchResults({ pages, files, groups })
1577
1628
 
1578
1629
  if (!skipTabUpdate) {
1579
1630
  const preferredTab = SEARCH_ROUTE_TABS.has(targetState.tab)
@@ -2362,15 +2413,7 @@ export default {
2362
2413
  this.aiTranscript = []
2363
2414
  this.anchoredUserTurnId = null
2364
2415
  }
2365
-
2366
- const searchStore = this.getSearchStore()
2367
- if (searchStore) {
2368
- await Promise.all([
2369
- searchStore.dispatch('search/setPages', []),
2370
- searchStore.dispatch('search/setFiles', []),
2371
- searchStore.dispatch('search/setGroups', []),
2372
- ])
2373
- }
2416
+ this.setSearchResults({ pages: [], files: [], groups: [] })
2374
2417
  },
2375
2418
 
2376
2419
  async clearInput() {
@@ -2399,55 +2442,58 @@ export default {
2399
2442
  },
2400
2443
 
2401
2444
  async searchPages(options = {}) {
2402
- const searchStore = this.getSearchStore()
2403
- if (searchStore) {
2404
- const keepAnswersTab = options.keepAnswersTab || this.keywordsFromAi
2405
- const searchInput = {
2406
- keywords: this.keywords,
2407
- question: this.searchQuestion || this.inputText,
2408
- }
2409
-
2410
- this.searchingPages = true
2411
- await searchStore.dispatch('search/queryPages', searchInput)
2412
- this.searchingPages = false
2413
-
2414
- if (this.keywords && this.keywords.trim() && !keepAnswersTab) {
2415
- this.currentTab = 'Pages'
2416
- this.scrollResultsToTop()
2417
- }
2445
+ const keepAnswersTab = options.keepAnswersTab || this.keywordsFromAi
2446
+ const searchInput = {
2447
+ keywords: this.keywords,
2448
+ question: this.searchQuestion || this.inputText,
2449
+ }
2450
+
2451
+ this.searchingPages = true
2452
+ await this.querySearchCollectionWithFallback({
2453
+ handler: this.queryPagesHandler,
2454
+ storeAction: 'search/queryPages',
2455
+ payload: searchInput,
2456
+ target: 'resolvedPages',
2457
+ })
2458
+ this.searchingPages = false
2418
2459
 
2419
- this.keywordsFromAi = false
2460
+ if (this.keywords && this.keywords.trim() && !keepAnswersTab) {
2461
+ this.currentTab = 'Pages'
2462
+ this.scrollResultsToTop()
2463
+ }
2420
2464
 
2421
- if (!keepAnswersTab) {
2422
- this.aiAnswer = null
2423
- this.aiSources = []
2424
- this.aiKeywordsDisplayAnswer = ''
2425
- this.aiKeywordsDisplayPages = ''
2426
- }
2465
+ this.keywordsFromAi = false
2427
2466
 
2428
- this.persistSearchCacheSnapshot()
2467
+ if (!keepAnswersTab) {
2468
+ this.aiAnswer = null
2469
+ this.aiSources = []
2470
+ this.aiKeywordsDisplayAnswer = ''
2471
+ this.aiKeywordsDisplayPages = ''
2429
2472
  }
2473
+
2474
+ this.persistSearchCacheSnapshot()
2430
2475
  },
2431
2476
 
2432
2477
  async searchFiles(options = {}) {
2433
- const searchStore = this.getSearchStore()
2434
- if (searchStore) {
2435
- const searchInput = {
2436
- keywords: this.keywords,
2437
- question: this.searchQuestion || this.inputText,
2438
- }
2439
- const owners = Array.isArray(options.owners) ? options.owners : []
2440
- if (owners.length > 0) searchInput.owners = owners
2441
-
2442
- this.searchingFiles = true
2443
- await searchStore.dispatch('search/queryFiles', searchInput)
2444
- this.searchingFiles = false
2445
- this.persistSearchCacheSnapshot()
2446
- }
2478
+ const searchInput = {
2479
+ keywords: this.keywords,
2480
+ question: this.searchQuestion || this.inputText,
2481
+ }
2482
+ const owners = Array.isArray(options.owners) ? options.owners : []
2483
+ if (owners.length > 0) searchInput.owners = owners
2484
+
2485
+ this.searchingFiles = true
2486
+ await this.querySearchCollectionWithFallback({
2487
+ handler: this.queryFilesHandler,
2488
+ storeAction: 'search/queryFiles',
2489
+ payload: searchInput,
2490
+ target: 'resolvedFiles',
2491
+ })
2492
+ this.searchingFiles = false
2493
+ this.persistSearchCacheSnapshot()
2447
2494
  },
2448
2495
 
2449
2496
  async searchFilesWithGroupOwners() {
2450
- if (!this.getSearchStore()) return
2451
2497
  const groupsPromise = this.searchGroups()
2452
2498
  await this.searchFiles()
2453
2499
  const ownerFilters = await groupsPromise
@@ -2457,28 +2503,28 @@ export default {
2457
2503
  },
2458
2504
 
2459
2505
  async searchGroups() {
2460
- const searchStore = this.getSearchStore()
2461
- if (searchStore) {
2462
- const searchInput = {
2463
- keywords: this.keywords,
2464
- question: this.searchQuestion || this.inputText,
2465
- }
2466
- await searchStore.dispatch('search/queryGroups', searchInput)
2467
- this.persistSearchCacheSnapshot()
2468
- return Array.from(
2469
- new Set(
2470
- (this.resolvedGroups || [])
2471
- .map(group => (group && group.title ? group.title.trim() : ''))
2472
- .filter(Boolean)
2473
- )
2506
+ const searchInput = {
2507
+ keywords: this.keywords,
2508
+ question: this.searchQuestion || this.inputText,
2509
+ }
2510
+ await this.querySearchCollectionWithFallback({
2511
+ handler: this.queryGroupsHandler,
2512
+ storeAction: 'search/queryGroups',
2513
+ payload: searchInput,
2514
+ target: 'resolvedGroups',
2515
+ })
2516
+ this.persistSearchCacheSnapshot()
2517
+ return Array.from(
2518
+ new Set(
2519
+ (this.resolvedGroups || [])
2520
+ .map(group => (group && group.title ? group.title.trim() : ''))
2521
+ .filter(Boolean)
2474
2522
  )
2475
- }
2476
- return []
2523
+ )
2477
2524
  },
2478
2525
 
2479
2526
  async onSubmitInput() {
2480
2527
  if (this.searchingAi) return
2481
- const searchStore = this.getSearchStore()
2482
2528
  const text = (this.inputText || '').trim()
2483
2529
  if (!text) {
2484
2530
  this.suppressAutoRestore = true
@@ -2533,7 +2579,7 @@ export default {
2533
2579
  this.organization && this.organization.title
2534
2580
  ? this.organization.title
2535
2581
  : null,
2536
- languageCode: searchStore ? searchStore.state.search.languageCode : 'en',
2582
+ languageCode: this.getSearchLanguageCode(),
2537
2583
  isFollowup: false,
2538
2584
  functionsEndpoint:
2539
2585
  this.$config && this.$config.functionsEndpoint
@@ -2590,7 +2636,6 @@ export default {
2590
2636
  typeof question === 'string' ? question.trim() : ''
2591
2637
  if (!normalizedQuestion) return
2592
2638
  if (this.searchingAi) return
2593
- const searchStore = this.getSearchStore()
2594
2639
  if (
2595
2640
  this.activeAiQuestionKey &&
2596
2641
  this.activeAiQuestionKey === normalizedQuestion
@@ -2788,7 +2833,7 @@ export default {
2788
2833
  this.organization && this.organization.title
2789
2834
  ? this.organization.title
2790
2835
  : null,
2791
- languageCode: searchStore ? searchStore.state.search.languageCode : 'en',
2836
+ languageCode: this.getSearchLanguageCode(),
2792
2837
  isFollowup: isFollowUpQuestion,
2793
2838
  functionsEndpoint:
2794
2839
  this.$config && this.$config.functionsEndpoint
@@ -172,7 +172,6 @@ import MqLayout from '../../utils/MqLayout'
172
172
 
173
173
  export default {
174
174
  name: 'Asd20AppTemplate',
175
- mixins: [responsiveBreakpointMixin],
176
175
  components: {
177
176
  Asd20AppHeader,
178
177
  Asd20Button,
@@ -184,6 +183,7 @@ export default {
184
183
  Asd20NotificationGroup,
185
184
  MqLayout,
186
185
  },
186
+ mixins: [responsiveBreakpointMixin],
187
187
 
188
188
  props: {
189
189
  title: { type: String, default: '' },
@@ -35,7 +35,7 @@
35
35
  :organization-options="organizationOptions"
36
36
  :language-code="languageCode"
37
37
  :breadcrumb-links="breadcrumbLinks"
38
- v-on="$listeners"
38
+ @update:languageCode="$emit('update:languageCode', $event)"
39
39
  >
40
40
  <!-- Floating Notifications -->
41
41
  <template #top>
@@ -35,7 +35,7 @@
35
35
  :organization-options="organizationOptions"
36
36
  :language-code="languageCode"
37
37
  :breadcrumb-links="breadcrumbLinks"
38
- v-on="$listeners"
38
+ @update:languageCode="$emit('update:languageCode', $event)"
39
39
  >
40
40
  <!-- Floating Notifications -->
41
41
  <template #top>
@@ -34,7 +34,7 @@
34
34
  :organization-options="organizationOptions"
35
35
  :language-code="languageCode"
36
36
  :breadcrumb-links="breadcrumbLinks"
37
- v-on="$listeners"
37
+ @update:languageCode="$emit('update:languageCode', $event)"
38
38
  >
39
39
  <!-- Floating Notifications -->
40
40
  <template #top>
@@ -38,7 +38,7 @@
38
38
  :publish-date-time="parentPage ? '' : publishDateTime"
39
39
  :modified-date-time="parentPage ? '' : modifiedDateTime"
40
40
  :categories="parentPage ? [] : messageCategories"
41
- v-on="$listeners"
41
+ @update:languageCode="$emit('update:languageCode', $event)"
42
42
  >
43
43
  <!-- Floating Notifications -->
44
44
  <template #top>
@@ -37,7 +37,7 @@
37
37
  :publish-date-time="publishDateTime"
38
38
  :categories="messageCategories"
39
39
  :language-code="languageCode"
40
- v-on="$listeners"
40
+ @update:languageCode="$emit('update:languageCode', $event)"
41
41
  >
42
42
  <!-- Floating Notifications -->
43
43
  <template #top>
@@ -23,7 +23,7 @@
23
23
  :breadcrumb-links="breadcrumbLinks"
24
24
  :publish-date-time="parentPage ? '' : publishDateTime"
25
25
  :categories="parentPage ? [] : messageCategories"
26
- v-on="$listeners"
26
+ @update:languageCode="$emit('update:languageCode', $event)"
27
27
  >
28
28
  <!-- Floating Notifications -->
29
29
  <template #top>
@@ -23,7 +23,7 @@
23
23
  :language-code="languageCode"
24
24
  :images="firstMessage.images"
25
25
  :breadcrumb-links="breadcrumbLinks"
26
- v-on="$listeners"
26
+ @update:languageCode="$emit('update:languageCode', $event)"
27
27
  >
28
28
  <template #top>
29
29
  <client-only>
@@ -35,7 +35,7 @@
35
35
  :organization-options="organizationOptions"
36
36
  :language-code="languageCode"
37
37
  :breadcrumb-links="breadcrumbLinks"
38
- v-on="$listeners"
38
+ @update:languageCode="$emit('update:languageCode', $event)"
39
39
  >
40
40
  <!-- Floating Notifications -->
41
41
  <template #top>
@@ -38,7 +38,7 @@
38
38
  :publish-date-time="parentPage ? '' : publishDateTime"
39
39
  :modified-date-time="parentPage ? '' : modifiedDateTime"
40
40
  :categories="parentPage ? [] : messageCategories"
41
- v-on="$listeners"
41
+ @update:languageCode="$emit('update:languageCode', $event)"
42
42
  >
43
43
  <!-- Floating Notifications -->
44
44
  <template #top>
@@ -38,7 +38,7 @@
38
38
  :publish-date-time="parentPage ? '' : publishDateTime"
39
39
  :modified-date-time="parentPage ? '' : modifiedDateTime"
40
40
  :categories="parentPage ? [] : messageCategories"
41
- v-on="$listeners"
41
+ @update:languageCode="$emit('update:languageCode', $event)"
42
42
  >
43
43
  <!-- Floating Notifications -->
44
44
  <template #top>
@@ -38,7 +38,7 @@
38
38
  :publish-date-time="parentPage ? '' : publishDateTime"
39
39
  :modified-date-time="parentPage ? '' : modifiedDateTime"
40
40
  :categories="parentPage ? [] : messageCategories"
41
- v-on="$listeners"
41
+ @update:languageCode="$emit('update:languageCode', $event)"
42
42
  >
43
43
  <!-- Floating Notifications -->
44
44
  <template #top>
@@ -38,7 +38,7 @@
38
38
  :publish-date-time="parentPage ? '' : publishDateTime"
39
39
  :modified-date-time="parentPage ? '' : modifiedDateTime"
40
40
  :categories="parentPage ? [] : messageCategories"
41
- v-on="$listeners"
41
+ @update:languageCode="$emit('update:languageCode', $event)"
42
42
  >
43
43
  <!-- Floating Notifications -->
44
44
  <template #top>
@@ -72,7 +72,7 @@
72
72
  reversed
73
73
  no-label
74
74
  :language-code="languageCode"
75
- v-on="$listeners"
75
+ @update:languageCode="$emit('update:languageCode', $event)"
76
76
  />
77
77
  </client-only>
78
78
 
@@ -55,8 +55,18 @@
55
55
  <client-only>
56
56
  <Asd20AiSearch
57
57
  class="ai-search"
58
+ :pages="searchPages"
59
+ :files="searchFiles"
60
+ :groups="searchGroups"
61
+ :include-district-results="includeDistrictResults"
62
+ :search-language-code="searchLanguageCode"
63
+ :query-pages-handler="queryPagesHandler"
64
+ :query-files-handler="queryFilesHandler"
65
+ :query-groups-handler="queryGroupsHandler"
66
+ :query-ai-site-handler="queryAiSiteHandler"
58
67
  :organization="organization"
59
68
  :organization-options="organizationOptions"
69
+ :ai-search-feedback-form-url="aiSearchFeedbackFormUrl"
60
70
  />
61
71
  </client-only>
62
72
 
@@ -88,7 +98,7 @@
88
98
  reversed
89
99
  no-label
90
100
  :language-code="languageCode"
91
- v-on="$listeners"
101
+ @update:languageCode="$emit('update:languageCode', $event)"
92
102
  />
93
103
  </client-only>
94
104
 
@@ -206,6 +216,16 @@ export default {
206
216
  mixins: [pageTemplateMixin],
207
217
  props: {
208
218
  languageCode: { type: String, default: 'en' },
219
+ searchPages: { type: Array, default: () => [] },
220
+ searchFiles: { type: Array, default: () => [] },
221
+ searchGroups: { type: Array, default: () => [] },
222
+ includeDistrictResults: { type: Boolean, default: true },
223
+ searchLanguageCode: { type: String, default: null },
224
+ queryPagesHandler: { type: Function, default: null },
225
+ queryFilesHandler: { type: Function, default: null },
226
+ queryGroupsHandler: { type: Function, default: null },
227
+ queryAiSiteHandler: { type: Function, default: null },
228
+ aiSearchFeedbackFormUrl: { type: String, default: '' },
209
229
  },
210
230
  data: () => ({
211
231
  factoids: [
@@ -36,7 +36,7 @@
36
36
  :breadcrumb-links="breadcrumbLinks"
37
37
  :publish-date-time="parentPage ? '' : publishDateTime"
38
38
  :categories="parentPage ? [] : messageCategories"
39
- v-on="$listeners"
39
+ @update:languageCode="$emit('update:languageCode', $event)"
40
40
  >
41
41
  <!-- Floating Notifications -->
42
42
  <template #top>
@@ -35,7 +35,7 @@
35
35
  :organization-options="organizationOptions"
36
36
  :breadcrumb-links="breadcrumbLinks"
37
37
  :language-code="languageCode"
38
- v-on="$listeners"
38
+ @update:languageCode="$emit('update:languageCode', $event)"
39
39
  >
40
40
  <!-- Floating Notifications -->
41
41
  <client-only>
@@ -35,7 +35,7 @@
35
35
  :organization-options="organizationOptions"
36
36
  :language-code="languageCode"
37
37
  :breadcrumb-links="breadcrumbLinks"
38
- v-on="$listeners"
38
+ @update:languageCode="$emit('update:languageCode', $event)"
39
39
  >
40
40
  <!-- Floating Notifications -->
41
41
  <template #top>
@@ -52,7 +52,7 @@
52
52
  :breadcrumb-links="breadcrumbLinks"
53
53
  :publish-date-time="parentPage ? '' : publishDateTime"
54
54
  :categories="parentPage ? [] : messageCategories"
55
- v-on="$listeners"
55
+ @update:languageCode="$emit('update:languageCode', $event)"
56
56
  >
57
57
  <!-- Floating Notifications -->
58
58
  <template #top>
@@ -37,7 +37,7 @@
37
37
  :breadcrumb-links="breadcrumbLinks"
38
38
  :publish-date-time="parentPage ? '' : publishDateTime"
39
39
  :categories="parentPage ? [] : messageCategories"
40
- v-on="$listeners"
40
+ @update:languageCode="$emit('update:languageCode', $event)"
41
41
  >
42
42
  <!-- Floating Notifications -->
43
43
  <template #top>
@@ -97,8 +97,18 @@
97
97
  <client-only>
98
98
  <Asd20AiSearch
99
99
  class="ai-search"
100
+ :pages="searchPages"
101
+ :files="searchFiles"
102
+ :groups="searchGroups"
103
+ :include-district-results="includeDistrictResults"
104
+ :search-language-code="searchLanguageCode"
105
+ :query-pages-handler="queryPagesHandler"
106
+ :query-files-handler="queryFilesHandler"
107
+ :query-groups-handler="queryGroupsHandler"
108
+ :query-ai-site-handler="queryAiSiteHandler"
100
109
  :organization="organization"
101
110
  :organization-options="organizationOptions"
111
+ :ai-search-feedback-form-url="aiSearchFeedbackFormUrl"
102
112
  />
103
113
  </client-only>
104
114
  <client-only>
@@ -114,7 +124,7 @@
114
124
  reversed
115
125
  no-label
116
126
  :language-code="languageCode"
117
- v-on="$listeners"
127
+ @update:languageCode="$emit('update:languageCode', $event)"
118
128
  />
119
129
  </client-only>
120
130
  <!-- Floating Notifications -->
@@ -218,6 +228,16 @@ export default {
218
228
  mixins: [pageTemplateMixin],
219
229
  props: {
220
230
  languageCode: { type: String, default: 'en' },
231
+ searchPages: { type: Array, default: () => [] },
232
+ searchFiles: { type: Array, default: () => [] },
233
+ searchGroups: { type: Array, default: () => [] },
234
+ includeDistrictResults: { type: Boolean, default: true },
235
+ searchLanguageCode: { type: String, default: null },
236
+ queryPagesHandler: { type: Function, default: null },
237
+ queryFilesHandler: { type: Function, default: null },
238
+ queryGroupsHandler: { type: Function, default: null },
239
+ queryAiSiteHandler: { type: Function, default: null },
240
+ aiSearchFeedbackFormUrl: { type: String, default: '' },
221
241
  },
222
242
 
223
243
  data: () => ({
@@ -96,8 +96,18 @@
96
96
  <client-only>
97
97
  <Asd20AiSearch
98
98
  class="ai-search"
99
+ :pages="searchPages"
100
+ :files="searchFiles"
101
+ :groups="searchGroups"
102
+ :include-district-results="includeDistrictResults"
103
+ :search-language-code="searchLanguageCode"
104
+ :query-pages-handler="queryPagesHandler"
105
+ :query-files-handler="queryFilesHandler"
106
+ :query-groups-handler="queryGroupsHandler"
107
+ :query-ai-site-handler="queryAiSiteHandler"
99
108
  :organization="organization"
100
109
  :organization-options="organizationOptions"
110
+ :ai-search-feedback-form-url="aiSearchFeedbackFormUrl"
101
111
  />
102
112
  </client-only>
103
113
 
@@ -115,7 +125,7 @@
115
125
  reversed
116
126
  no-label
117
127
  :language-code="languageCode"
118
- v-on="$listeners"
128
+ @update:languageCode="$emit('update:languageCode', $event)"
119
129
  />
120
130
  </client-only>
121
131
 
@@ -221,6 +231,16 @@ export default {
221
231
  mixins: [pageTemplateMixin],
222
232
  props: {
223
233
  languageCode: { type: String, default: 'en' },
234
+ searchPages: { type: Array, default: () => [] },
235
+ searchFiles: { type: Array, default: () => [] },
236
+ searchGroups: { type: Array, default: () => [] },
237
+ includeDistrictResults: { type: Boolean, default: true },
238
+ searchLanguageCode: { type: String, default: null },
239
+ queryPagesHandler: { type: Function, default: null },
240
+ queryFilesHandler: { type: Function, default: null },
241
+ queryGroupsHandler: { type: Function, default: null },
242
+ queryAiSiteHandler: { type: Function, default: null },
243
+ aiSearchFeedbackFormUrl: { type: String, default: '' },
224
244
  },
225
245
  computed: {
226
246
  blockEvent() {
@@ -112,7 +112,6 @@ import MqLayout from '../../utils/MqLayout'
112
112
 
113
113
  export default {
114
114
  name: 'Asd20SearchAppTemplate',
115
- mixins: [responsiveBreakpointMixin],
116
115
  components: {
117
116
  Asd20Viewport,
118
117
  Asd20Modal,
@@ -122,6 +121,7 @@ export default {
122
121
  Asd20Button,
123
122
  MqLayout,
124
123
  },
124
+ mixins: [responsiveBreakpointMixin],
125
125
 
126
126
  props: {
127
127
  resultCount: { type: Number, default: 0 },
@@ -35,7 +35,7 @@
35
35
  :organization-options="organizationOptions"
36
36
  :language-code="languageCode"
37
37
  :breadcrumb-links="breadcrumbLinks"
38
- v-on="$listeners"
38
+ @update:languageCode="$emit('update:languageCode', $event)"
39
39
  >
40
40
  <!-- Floating Notifications -->
41
41
  <template #top>
@@ -35,7 +35,7 @@
35
35
  :organization-options="organizationOptions"
36
36
  :language-code="languageCode"
37
37
  :breadcrumb-links="breadcrumbLinks"
38
- v-on="$listeners"
38
+ @update:languageCode="$emit('update:languageCode', $event)"
39
39
  >
40
40
  <!-- Floating Notifications -->
41
41
  <template #top>
@@ -35,7 +35,7 @@
35
35
  :organization-options="organizationOptions"
36
36
  :language-code="languageCode"
37
37
  :breadcrumb-links="breadcrumbLinks"
38
- v-on="$listeners"
38
+ @update:languageCode="$emit('update:languageCode', $event)"
39
39
  >
40
40
  <!-- Floating Notifications -->
41
41
  <template #top>
@@ -35,7 +35,7 @@
35
35
  :organization-options="organizationOptions"
36
36
  :language-code="languageCode"
37
37
  :breadcrumb-links="breadcrumbLinks"
38
- v-on="$listeners"
38
+ @update:languageCode="$emit('update:languageCode', $event)"
39
39
  >
40
40
  <!-- Floating Notifications -->
41
41
  <template #top>
@@ -1,37 +1,80 @@
1
1
  // pass a variable, the prop options as it would be defined in vue, and optionally the store module, which will become a module named for convenience in the store.
2
2
 
3
+ function hyphenate(value) {
4
+ return value.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
5
+ }
6
+
7
+ function resolvePropDefault(propOptions = {}) {
8
+ const defaultValue = propOptions.default
9
+ if (typeof defaultValue === 'function' && propOptions.type !== Function) {
10
+ return defaultValue()
11
+ }
12
+ return defaultValue
13
+ }
14
+
15
+ function hasIncomingProp(vm, propName) {
16
+ const rawProps = vm && vm.$ && vm.$.vnode && vm.$.vnode.props
17
+ if (!rawProps) return false
18
+
19
+ return (
20
+ Object.prototype.hasOwnProperty.call(rawProps, propName) ||
21
+ Object.prototype.hasOwnProperty.call(rawProps, hyphenate(propName))
22
+ )
23
+ }
24
+
3
25
  export default function(variableName, propOptions, storeModuleName) {
4
26
  const pascalCaseName = variableName[0].toUpperCase() + variableName.slice(1)
5
27
  const resolvedName = `resolved${pascalCaseName}`
28
+ const localStateName = `local${pascalCaseName}`
6
29
  let mixin = { props: {}, computed: {}, methods: {} }
7
30
  mixin.props[variableName] = propOptions
31
+ mixin.data = function() {
32
+ return {
33
+ [localStateName]: resolvePropDefault(propOptions),
34
+ }
35
+ }
8
36
  // Returns a computed alias that resolves from the store when available,
9
37
  // otherwise falling back to the local prop value.
10
38
  mixin.computed[resolvedName] = {
11
39
  get() {
40
+ if (hasIncomingProp(this, variableName)) {
41
+ return this[variableName]
42
+ }
43
+
12
44
  try {
13
- return storeModuleName
45
+ const storeValue = storeModuleName
14
46
  ? this.$store['state'][storeModuleName][variableName]
15
47
  : this.$store.state[variableName]
48
+ return storeValue !== undefined ? storeValue : this[localStateName]
16
49
  } catch {
17
- return this[variableName]
50
+ return this[localStateName]
18
51
  }
19
52
  },
20
53
  // emit an update event and value and call method to set value in store.
21
54
  set(value) {
22
55
  this.$emit(`update:${variableName}`, value)
56
+
57
+ if (hasIncomingProp(this, variableName)) {
58
+ return
59
+ }
60
+
23
61
  try {
24
- this[`set${pascalCaseName}`](value)
62
+ if (this[`set${pascalCaseName}`](value)) return
25
63
  } catch {
26
64
  // throw away
27
65
  }
66
+
67
+ this[localStateName] = value
28
68
  },
29
69
  }
30
70
  mixin.methods[`set${pascalCaseName}`] = function(value) {
71
+ if (!this.$store || typeof this.$store.dispatch !== 'function') return false
72
+
31
73
  this.$store.dispatch(
32
74
  `${storeModuleName ? storeModuleName + '/' : ''}set${pascalCaseName}`,
33
75
  value
34
76
  )
77
+ return true
35
78
  }
36
79
  return mixin
37
80
  }
@@ -1,7 +1,21 @@
1
+ function hasIncomingProp(vm, propName) {
2
+ const rawProps = vm && vm.$ && vm.$.vnode && vm.$.vnode.props
3
+ if (!rawProps) return false
4
+
5
+ return (
6
+ Object.prototype.hasOwnProperty.call(rawProps, propName) ||
7
+ Object.prototype.hasOwnProperty.call(rawProps, 'is-loading')
8
+ )
9
+ }
10
+
1
11
  export default function(variableName, storeModuleName) {
2
12
  let mixin = { props: {}, computed: {} }
3
13
  mixin.props.isLoading = { type: Boolean, default: false }
4
14
  mixin.computed[`_${variableName}`] = function() {
15
+ if (hasIncomingProp(this, 'isLoading')) {
16
+ return this.isLoading
17
+ }
18
+
5
19
  if (
6
20
  storeModuleName &&
7
21
  this.$store &&