@asd20/ui-next 1.0.10 → 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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,7 @@
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
+
3
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)
4
6
 
5
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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asd20/ui-next",
3
- "version": "1.0.10",
3
+ "version": "1.0.11",
4
4
  "private": false,
5
5
  "description": "ASD20 UI component library migration workspace.",
6
6
  "license": "MIT",
@@ -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: '' },
@@ -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 ||
@@ -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
@@ -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
 
@@ -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: [
@@ -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>
@@ -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
 
@@ -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() {
@@ -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 &&