@asd20/ui-next 2.10.11 → 2.11.0

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,12 @@
1
1
  # Changelog
2
2
 
3
+ # [2.11.0](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.10.11...ui-next-v2.11.0) (2026-05-26)
4
+
5
+
6
+ ### Features
7
+
8
+ * allow ai search pill to pass search parameters to nav search; remove secondary ai search modal ([4bd6620](https://github.com/academydistrict20/asd20-ui-next/commit/4bd6620a72c75b26fdd6d9256152e35065e5c7d2))
9
+
3
10
  ## [2.10.11](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.10.10...ui-next-v2.10.11) (2026-05-20)
4
11
 
5
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asd20/ui-next",
3
- "version": "2.10.11",
3
+ "version": "2.11.0",
4
4
  "private": false,
5
5
  "description": "ASD20 UI component library for Vue 3.",
6
6
  "license": "MIT",
@@ -1,22 +1,12 @@
1
1
  <template>
2
- <div
3
- ref="container"
4
- class="asd20-ai-search"
5
- :class="{ 'asd20-ai-search--expanded': expanded }"
6
- :style="rootStyle"
7
- >
8
- <div
9
- class="asd20-ai-search__pill"
10
- :class="{ 'asd20-ai-search__pill--hidden': expanded || isClosing }"
11
- >
2
+ <div class="asd20-ai-search">
3
+ <div class="asd20-ai-search__pill">
12
4
  <div class="asd20-ai-search__question-row">
13
5
  <div class="asd20-ai-search__query">
14
6
  <asd20-search-field
15
7
  v-model="draftQuery"
16
8
  id-tag="-ai-home"
17
9
  :placeholder="placeholder"
18
- @click="onPillInputInteraction"
19
- @focusin="onPillInputInteraction"
20
10
  @keyup.enter.stop.prevent="onSubmit"
21
11
  />
22
12
  <button
@@ -39,81 +29,23 @@
39
29
  size="md"
40
30
  bordered
41
31
  reversed
42
- :disabled="!canSubmitOrResume"
32
+ :disabled="!hasDraftQuery"
43
33
  @click.stop="onSubmit"
44
34
  />
45
35
  </div>
46
36
  </div>
47
-
48
- <transition
49
- name="ai-search-panel"
50
- @before-leave="onBeforePanelLeave"
51
- @after-leave="onAfterPanelLeave"
52
- >
53
- <div
54
- v-show="expanded"
55
- class="asd20-ai-search__panel"
56
- >
57
- <button
58
- type="button"
59
- class="asd20-ai-search__backdrop"
60
- aria-label="Close AI search"
61
- @click.stop="collapse"
62
- />
63
-
64
- <button
65
- type="button"
66
- class="asd20-ai-search__close"
67
- aria-label="Close AI search"
68
- @click.stop="collapse"
69
- >
70
- <asd20-icon
71
- name="close"
72
- size="sm"
73
- />
74
- </button>
75
-
76
- <asd20-site-search
77
- ref="siteSearch"
78
- class="asd20-ai-search__site-search"
79
- :active="expanded"
80
- :inline="true"
81
- :show-contacts="showContacts"
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"
91
- :organization="organization"
92
- :organization-options="organizationOptions"
93
- :ai-search-feedback-form-url="aiSearchFeedbackFormUrl"
94
- @update:active="onSearchActiveUpdate"
95
- />
96
- </div>
97
- </transition>
98
37
  </div>
99
38
  </template>
100
39
 
101
40
  <script>
102
41
  import Asd20SearchField from '../../molecules/Asd20SearchField'
103
- import Asd20SiteSearch from '../Asd20SiteSearch'
104
42
  import Asd20Icon from '../../atoms/Asd20Icon'
105
43
  import Asd20Button from '../../atoms/Asd20Button'
106
44
 
107
- const SEARCH_SESSION_STORAGE_KEY = 'asd20-site-search-state-v1'
108
- const SEARCH_STATE_SYNC_EVENT = 'asd20-site-search:state-sync'
109
- const PHONE_MAX_WIDTH = 767
110
- const PANEL_WIDTH_RATIO = 0.8
111
-
112
45
  export default {
113
46
  name: 'Asd20AiSearch',
114
47
  components: {
115
48
  Asd20SearchField,
116
- Asd20SiteSearch,
117
49
  Asd20Icon,
118
50
  Asd20Button,
119
51
  },
@@ -136,335 +68,26 @@ export default {
136
68
  default: 'What can we help you find?',
137
69
  },
138
70
  },
71
+ emits: ['submit-search'],
139
72
  data: () => ({
140
- expanded: false,
141
73
  draftQuery: '',
142
- screenRightOffset: 0,
143
- navLeftOffset: 0,
144
- panelWidth: 0,
145
- panelRightFromContainer: 0,
146
- panelOriginX: 0,
147
- panelOriginY: 0,
148
- isClosing: false,
149
- resumeStateAvailable: false,
150
74
  }),
151
75
  computed: {
152
- rootStyle() {
153
- return {
154
- '--asd20-ai-search-screen-right-offset': `${this.screenRightOffset}px`,
155
- '--asd20-ai-search-nav-left-offset': `${this.navLeftOffset}px`,
156
- '--asd20-ai-search-panel-width': `${this.panelWidth}px`,
157
- '--asd20-ai-search-panel-right-from-container': `${this.panelRightFromContainer}px`,
158
- '--asd20-ai-search-origin-x': `${this.panelOriginX}px`,
159
- '--asd20-ai-search-origin-y': `${this.panelOriginY}px`,
160
- }
161
- },
162
-
163
76
  hasDraftQuery() {
164
77
  return !!(this.draftQuery || '').trim()
165
78
  },
166
-
167
- canSubmitOrResume() {
168
- return this.hasDraftQuery || this.resumeStateAvailable
169
- },
170
- },
171
- mounted() {
172
- this.updatePanelLayout()
173
- this.updatePanelOrigin()
174
- this.updateResumeStateAvailability()
175
- if (typeof document !== 'undefined') {
176
- document.addEventListener('mousedown', this.onDocumentPointerDown)
177
- document.addEventListener('touchstart', this.onDocumentPointerDown, {
178
- passive: true,
179
- })
180
- }
181
- if (typeof window !== 'undefined') {
182
- window.addEventListener('resize', this.onWindowResize, { passive: true })
183
- window.addEventListener('orientationchange', this.onWindowResize, {
184
- passive: true,
185
- })
186
- window.addEventListener(SEARCH_STATE_SYNC_EVENT, this.onSearchStateSync)
187
- window.addEventListener('focus', this.onWindowFocus, { passive: true })
188
- }
189
- },
190
- beforeUnmount() {
191
- this.teardownWindowBindings()
192
79
  },
193
80
  methods: {
194
- teardownWindowBindings() {
195
- if (typeof document !== 'undefined') {
196
- document.removeEventListener('mousedown', this.onDocumentPointerDown)
197
- document.removeEventListener('touchstart', this.onDocumentPointerDown)
198
- }
199
- if (typeof window !== 'undefined') {
200
- window.removeEventListener('resize', this.onWindowResize)
201
- window.removeEventListener('orientationchange', this.onWindowResize)
202
- window.removeEventListener(
203
- SEARCH_STATE_SYNC_EVENT,
204
- this.onSearchStateSync
205
- )
206
- window.removeEventListener('focus', this.onWindowFocus)
207
- }
208
- },
209
- onWindowResize() {
210
- if (typeof window !== 'undefined' && window.innerWidth < 1024) {
211
- this.collapse()
212
- }
213
- this.updatePanelLayout()
214
- if (!this.expanded) {
215
- this.updatePanelOrigin()
216
- }
217
- },
218
-
219
- updateScreenRightOffset() {
220
- if (typeof window === 'undefined') return
221
- if (!this.$refs.container || !this.$refs.container.getBoundingClientRect)
222
- return
223
- const rect = this.$refs.container.getBoundingClientRect()
224
- this.screenRightOffset = Math.max(0, window.innerWidth - rect.right)
225
- },
226
-
227
- updateNavLeftOffset() {
228
- if (typeof window === 'undefined' || typeof document === 'undefined') {
229
- return
230
- }
231
-
232
- if (window.innerWidth < 1024) {
233
- this.navLeftOffset = 0
234
- return
235
- }
236
-
237
- const nav = document.getElementById('site-navigation')
238
- if (!nav || !nav.getBoundingClientRect) {
239
- this.navLeftOffset = 0
240
- return
241
- }
242
-
243
- const rect = nav.getBoundingClientRect()
244
- const rightEdge = Math.max(0, rect.right || 0)
245
- this.navLeftOffset =
246
- rightEdge > 0 && rightEdge < window.innerWidth * 0.5 ? rightEdge : 0
247
- },
248
-
249
- panelLayoutForViewport(containerRect) {
250
- if (!containerRect || typeof window === 'undefined') {
251
- return { width: 0, rightFromContainer: 0 }
252
- }
253
-
254
- const viewportWidth = window.innerWidth
255
- const rightOffset = Math.max(0, window.innerWidth - containerRect.right)
256
-
257
- if (viewportWidth <= PHONE_MAX_WIDTH) {
258
- return {
259
- width: viewportWidth,
260
- rightFromContainer: -rightOffset,
261
- }
262
- }
263
-
264
- const navRightEdge =
265
- viewportWidth >= 1024 ? Math.max(0, this.navLeftOffset) : 0
266
- const maxAvailableWidth = Math.max(0, viewportWidth - navRightEdge)
267
- const targetWidth = viewportWidth * PANEL_WIDTH_RATIO
268
- const width = Math.max(0, Math.min(targetWidth, maxAvailableWidth))
269
- const desiredViewportRightGap = Math.max(
270
- 0,
271
- (viewportWidth - navRightEdge - width) / 2
272
- )
273
-
274
- return {
275
- width,
276
- rightFromContainer: desiredViewportRightGap - rightOffset,
277
- }
278
- },
279
-
280
- updatePanelLayout() {
281
- this.updateScreenRightOffset()
282
- this.updateNavLeftOffset()
283
- if (!this.$refs.container || !this.$refs.container.getBoundingClientRect) {
284
- return
285
- }
286
-
287
- const rect = this.$refs.container.getBoundingClientRect()
288
- const { width, rightFromContainer } = this.panelLayoutForViewport(rect)
289
- this.panelWidth = width
290
- this.panelRightFromContainer = rightFromContainer
291
- },
292
-
293
- updatePanelOrigin() {
294
- if (typeof window === 'undefined') return
295
- if (!this.$refs.container || !this.$refs.container.getBoundingClientRect) {
296
- return
297
- }
298
-
299
- const goButton = this.$el.querySelector('.asd20-ai-search__go-button')
300
- if (!goButton || !goButton.getBoundingClientRect) return
301
-
302
- const containerRect = this.$refs.container.getBoundingClientRect()
303
- const goRect = goButton.getBoundingClientRect()
304
- const { width, rightFromContainer } =
305
- this.panelLayoutForViewport(containerRect)
306
- const panelWidth = width
307
- const panelLeft =
308
- containerRect.right - rightFromContainer - panelWidth
309
-
310
- const originX = goRect.left + goRect.width / 2 - panelLeft
311
- const originY = goRect.top + goRect.height / 2 - containerRect.top
312
-
313
- this.panelOriginX = Math.max(0, Math.min(panelWidth, originX))
314
- this.panelOriginY = Math.max(0, originY)
315
- },
316
-
317
- onDocumentPointerDown(event) {
318
- if (!this.expanded) return
319
- if (!this.$refs.container) return
320
- if (!this.$refs.container.contains(event.target)) {
321
- this.collapse()
322
- }
323
- },
324
-
325
81
  onSubmit() {
326
82
  const text = (this.draftQuery || '').trim()
327
- this.expand({ submit: !!text })
328
- },
329
-
330
- onWindowFocus() {
331
- this.updateResumeStateAvailability()
332
- },
333
-
334
- onSearchStateSync() {
335
- this.updateResumeStateAvailability()
336
- },
337
-
338
- getSearchSessionStorage() {
339
- if (typeof window === 'undefined' || !window.sessionStorage) return null
340
- try {
341
- return window.sessionStorage
342
- } catch (error) {
343
- return null
344
- }
345
- },
346
-
347
- hasPersistedSearchState() {
348
- const storage = this.getSearchSessionStorage()
349
- if (!storage) return false
350
-
351
- try {
352
- const raw = storage.getItem(SEARCH_SESSION_STORAGE_KEY)
353
- if (!raw) return false
354
- const parsed = JSON.parse(raw)
355
- if (!parsed || typeof parsed !== 'object') return false
356
- const state = parsed.state && typeof parsed.state === 'object'
357
- ? parsed.state
358
- : parsed
359
- const text = typeof state.text === 'string' ? state.text.trim() : ''
360
- return !!text
361
- } catch (error) {
362
- return false
363
- }
364
- },
365
-
366
- hasExistingConversation() {
367
- const siteSearch = this.$refs.siteSearch
368
- if (!siteSearch) return false
369
- if (Array.isArray(siteSearch.aiTranscript) && siteSearch.aiTranscript.length) {
370
- return true
371
- }
372
- return !!siteSearch.hasAssistantTurns
373
- },
374
-
375
- hasExistingResults() {
376
- const siteSearch = this.$refs.siteSearch
377
- if (!siteSearch) return false
378
- const pages = Array.isArray(siteSearch.currentPages)
379
- ? siteSearch.currentPages
380
- : siteSearch.resolvedPages
381
- const files = Array.isArray(siteSearch.currentFiles)
382
- ? siteSearch.currentFiles
383
- : siteSearch.resolvedFiles
384
- const groups = Array.isArray(siteSearch.currentGroups)
385
- ? siteSearch.currentGroups
386
- : siteSearch.resolvedGroups
387
- if (
388
- Array.isArray(pages) &&
389
- pages.length
390
- ) {
391
- return true
392
- }
393
- if (
394
- Array.isArray(files) &&
395
- files.length
396
- ) {
397
- return true
398
- }
399
- if (
400
- Array.isArray(groups) &&
401
- groups.length
402
- ) {
403
- return true
404
- }
405
- return false
406
- },
407
-
408
- hasResumeState() {
409
- return (
410
- this.hasExistingConversation() ||
411
- this.hasExistingResults() ||
412
- this.hasPersistedSearchState()
413
- )
414
- },
415
-
416
- updateResumeStateAvailability() {
417
- this.resumeStateAvailable = this.hasResumeState()
418
- },
419
-
420
- onPillInputInteraction() {
421
- if (this.expanded || this.isClosing) return
422
- this.updateResumeStateAvailability()
423
- if (!this.hasResumeState()) return
424
- this.expand({ submit: false })
425
- },
426
-
427
- onSearchActiveUpdate(val) {
428
- if (!val) this.collapse()
429
- },
430
-
431
- collapse() {
432
- if (!this.expanded) return
433
- this.expanded = false
434
- this.draftQuery = ''
435
- this.updateResumeStateAvailability()
436
- },
437
-
438
- onBeforePanelLeave() {
439
- this.isClosing = true
440
- },
441
-
442
- onAfterPanelLeave() {
443
- this.isClosing = false
444
- },
445
-
446
- expand(options = {}) {
447
- const shouldSubmit = !!options.submit
448
- this.isClosing = false
449
- this.updatePanelLayout()
450
- this.updatePanelOrigin()
451
- this.expanded = true
83
+ if (!text) return
452
84
 
453
- this.$nextTick(() => {
454
- const siteSearch = this.$refs.siteSearch
455
- if (!siteSearch) return
456
-
457
- const text = this.draftQuery
458
- if (typeof siteSearch.applyInputText === 'function') {
459
- siteSearch.applyInputText(text, { submit: shouldSubmit })
460
- }
461
-
462
- if (shouldSubmit) {
463
- this.draftQuery = ''
464
- }
465
-
466
- this.updateResumeStateAvailability()
85
+ this.$emit('submit-search', {
86
+ text,
87
+ submit: true,
88
+ requestedAt: Date.now(),
467
89
  })
90
+ this.draftQuery = ''
468
91
  },
469
92
  },
470
93
  }
@@ -526,16 +149,23 @@ export default {
526
149
  }
527
150
  }
528
151
 
529
- &__pill--hidden {
530
- opacity: 0;
531
- visibility: hidden;
532
- pointer-events: none;
533
- }
534
-
535
152
  &__pill:focus-within {
536
153
  box-shadow: 0 0 0 2px var(--color__accent-t70);
537
154
  }
538
155
 
156
+ &__question-row {
157
+ display: flex;
158
+ align-items: center;
159
+ width: 100%;
160
+ }
161
+
162
+ &__query {
163
+ position: relative;
164
+ flex: 1 1 auto;
165
+ min-width: 0;
166
+ padding-left: 0.5rem;
167
+ }
168
+
539
169
  &__icon-button {
540
170
  position: absolute;
541
171
  top: 50%;
@@ -552,19 +182,6 @@ export default {
552
182
  color: var(--color__primary-t50);
553
183
  }
554
184
 
555
- &__question-row {
556
- display: flex;
557
- align-items: center;
558
- width: 100%;
559
- }
560
-
561
- &__query {
562
- position: relative;
563
- flex: 1 1 auto;
564
- min-width: 0;
565
- padding-left: 0.5rem;
566
- }
567
-
568
185
  &__go-button {
569
186
  flex: 0 0 auto;
570
187
  border-radius: 3rem;
@@ -584,133 +201,22 @@ export default {
584
201
  line-height: 1;
585
202
  }
586
203
 
587
- &__icon-button:focus,
588
- &__close:focus {
204
+ &__icon-button:focus {
589
205
  outline: none;
590
206
  box-shadow: 0 0 0 2px var(--color__accent-t70);
591
207
  border-radius: 999px;
592
208
  }
593
-
594
- &__panel {
595
- position: absolute;
596
- top: 0;
597
- left: 0;
598
- right: 0;
599
- z-index: 8;
600
- min-height: 75vh;
601
- height: 75vh;
602
- }
603
-
604
- &__backdrop {
605
- position: fixed;
606
- inset: 0;
607
- border: 0;
608
- margin: 0;
609
- padding: 0;
610
- background: var(--color__primary);
611
- opacity: 0.45;
612
- z-index: 7;
613
- cursor: pointer;
614
- }
615
-
616
- &__site-search {
617
- position: relative;
618
- z-index: 8;
619
- height: 100%;
620
-
621
- :deep(.asd20-site-search__ai-disclaimer-text) {
622
- padding-right: 3rem;
623
- }
624
- }
625
-
626
- &__close {
627
- position: absolute;
628
- top: space(0.625);
629
- right: space(0.625);
630
- z-index: 9;
631
- width: 1.75rem;
632
- height: 1.75rem;
633
- border-radius: 999px;
634
- border: 1px solid var(--color__tertiary);
635
- background: transparent;
636
- cursor: pointer;
637
- display: inline-flex;
638
- align-items: center;
639
- justify-content: center;
640
- }
641
- }
642
-
643
- .ai-search-panel-enter-active {
644
- transition: opacity 380ms ease;
645
- }
646
-
647
- .ai-search-panel-leave-active {
648
- transition: opacity 440ms ease;
649
209
  }
650
210
 
651
- .ai-search-panel-enter,
652
- .ai-search-panel-enter-from,
653
- .ai-search-panel-leave-to {
654
- opacity: 0;
655
- }
656
-
657
- .ai-search-panel-enter-active .asd20-ai-search__site-search,
658
- .ai-search-panel-enter-active .asd20-ai-search__close {
659
- transition:
660
- transform 520ms cubic-bezier(0.22, 0.8, 0.22, 1),
661
- opacity 380ms ease;
662
- transform-origin: var(--asd20-ai-search-origin-x, calc(100% - 2.5rem))
663
- var(--asd20-ai-search-origin-y, 1.375rem);
664
- will-change: transform, opacity;
665
- }
666
-
667
- .ai-search-panel-leave-active .asd20-ai-search__site-search,
668
- .ai-search-panel-leave-active .asd20-ai-search__close {
669
- transition:
670
- transform 460ms cubic-bezier(0.4, 0, 1, 1),
671
- opacity 440ms ease;
672
- transform-origin: var(--asd20-ai-search-origin-x, calc(100% - 2.5rem))
673
- var(--asd20-ai-search-origin-y, 1.375rem);
674
- will-change: transform, opacity;
675
- }
676
-
677
- .ai-search-panel-enter .asd20-ai-search__site-search,
678
- .ai-search-panel-enter-from .asd20-ai-search__site-search,
679
- .ai-search-panel-leave-to .asd20-ai-search__site-search,
680
- .ai-search-panel-enter .asd20-ai-search__close,
681
- .ai-search-panel-enter-from .asd20-ai-search__close,
682
- .ai-search-panel-leave-to .asd20-ai-search__close {
683
- transform: scale3d(0.16, 0.1, 1);
684
- opacity: 0;
685
- }
686
-
687
- .ai-search-panel-enter-active .asd20-ai-search__backdrop,
688
- .ai-search-panel-leave-active .asd20-ai-search__backdrop {
689
- transition: opacity 440ms ease;
690
- }
691
-
692
- .ai-search-panel-enter .asd20-ai-search__backdrop,
693
- .ai-search-panel-enter-from .asd20-ai-search__backdrop,
694
- .ai-search-panel-leave-to .asd20-ai-search__backdrop {
695
- opacity: 0;
696
- }
697
-
698
- @media (max-width: 1023px) {
211
+ @media (max-width: 479px) {
699
212
  .asd20-ai-search {
700
- display: none;
213
+ min-width: 0;
701
214
  }
702
215
  }
703
216
 
704
- @media (min-width: 667px) {
217
+ @media (max-width: 1023px) {
705
218
  .asd20-ai-search {
706
- &__panel {
707
- width: var(
708
- --asd20-ai-search-panel-width,
709
- calc(100vw - (2 * var(--asd20-ai-search-screen-right-offset, 0px)))
710
- );
711
- left: auto;
712
- right: var(--asd20-ai-search-panel-right-from-container, 0px);
713
- }
219
+ display: none;
714
220
  }
715
221
  }
716
222
  </style>
@@ -65,9 +65,21 @@
65
65
  <transition name="slide">
66
66
  <asd20-site-search
67
67
  v-show="searchOpen"
68
+ ref="siteSearch"
68
69
  :active="searchOpen"
70
+ :show-contacts="showContacts"
71
+ :pages="pages"
72
+ :files="files"
73
+ :groups="groups"
74
+ :include-district-results="includeDistrictResults"
75
+ :search-language-code="searchLanguageCode"
76
+ :query-pages-handler="queryPagesHandler"
77
+ :query-files-handler="queryFilesHandler"
78
+ :query-groups-handler="queryGroupsHandler"
79
+ :query-ai-site-handler="queryAiSiteHandler"
69
80
  :organization="organization"
70
81
  :organization-options="organizationOptions"
82
+ :ai-search-feedback-form-url="aiSearchFeedbackFormUrl"
71
83
  @update:active="$emit('update:searchOpen', $event)"
72
84
  >
73
85
  </asd20-site-search>
@@ -102,14 +114,27 @@ export default {
102
114
  condensed: { type: Boolean, default: false },
103
115
  menuOpen: { type: Boolean, default: false },
104
116
  searchOpen: { type: Boolean, default: false },
117
+ searchRequest: { type: Object, default: null },
118
+ pages: { type: Array, default: () => [] },
119
+ files: { type: Array, default: () => [] },
120
+ groups: { type: Array, default: () => [] },
121
+ showContacts: { type: Boolean, default: true },
122
+ includeDistrictResults: { type: Boolean, default: true },
123
+ searchLanguageCode: { type: String, default: null },
124
+ queryPagesHandler: { type: Function, default: null },
125
+ queryFilesHandler: { type: Function, default: null },
126
+ queryGroupsHandler: { type: Function, default: null },
127
+ queryAiSiteHandler: { type: Function, default: null },
105
128
  organization: { type: Object, default: null },
106
129
  organizationOptions: { type: Array, default: () => [] },
130
+ aiSearchFeedbackFormUrl: { type: String, default: '' },
107
131
  },
108
132
  data: () => ({
109
133
  keyboardTriggeredLastAction: false,
110
134
  desktop: false,
111
135
  isLoaded: false,
112
136
  zoomed: false,
137
+ pendingSearchRequest: null,
113
138
  }),
114
139
  computed: {
115
140
  emptyNav() {
@@ -124,6 +149,14 @@ export default {
124
149
  }
125
150
  })
126
151
  },
152
+ searchOpen: function (val) {
153
+ if (val) {
154
+ this.$nextTick(() => this.applyPendingSearchRequest())
155
+ }
156
+ },
157
+ searchRequest: function (request) {
158
+ this.queueSearchRequest(request)
159
+ },
127
160
  },
128
161
  mounted() {
129
162
  this.desktop = window.innerWidth >= 1024
@@ -153,6 +186,45 @@ export default {
153
186
  this.desktop = window.innerWidth >= 1024
154
187
  this.zoomed = window.innerHeight <= 650
155
188
  },
189
+ normalizeSearchRequest(request) {
190
+ if (!request || typeof request !== 'object') return null
191
+ const text = typeof request.text === 'string' ? request.text.trim() : ''
192
+ if (!text) return null
193
+
194
+ return {
195
+ text,
196
+ submit: request.submit !== false,
197
+ }
198
+ },
199
+ queueSearchRequest(request) {
200
+ const nextRequest = this.normalizeSearchRequest(request)
201
+ if (!nextRequest) return
202
+
203
+ this.pendingSearchRequest = nextRequest
204
+ const siteSearch = this.$refs.siteSearch
205
+ if (typeof siteSearch?.prepareProgrammaticInput === 'function') {
206
+ siteSearch.prepareProgrammaticInput()
207
+ }
208
+
209
+ if (this.menuOpen) {
210
+ this.$emit('update:menuOpen', false)
211
+ }
212
+ if (!this.searchOpen) {
213
+ this.$emit('update:searchOpen', true)
214
+ }
215
+
216
+ this.$nextTick(() => this.applyPendingSearchRequest())
217
+ },
218
+ applyPendingSearchRequest() {
219
+ if (!this.searchOpen || !this.pendingSearchRequest) return
220
+
221
+ const siteSearch = this.$refs.siteSearch
222
+ if (typeof siteSearch?.applyInputText !== 'function') return
223
+
224
+ const request = this.pendingSearchRequest
225
+ this.pendingSearchRequest = null
226
+ siteSearch.applyInputText(request.text, { submit: request.submit })
227
+ },
156
228
  toggleSearch() {
157
229
  this.$emit('update:searchOpen', !this.searchOpen)
158
230
  },
@@ -1934,9 +1934,14 @@ export default {
1934
1934
  }
1935
1935
  },
1936
1936
 
1937
+ prepareProgrammaticInput() {
1938
+ this.suppressAutoRestore = true
1939
+ },
1940
+
1937
1941
  applyInputText(text, options = {}) {
1938
1942
  const submit = !!options.submit
1939
1943
  const nextText = typeof text === 'string' ? text : ''
1944
+ this.prepareProgrammaticInput()
1940
1945
  this.suppressInputClearReset = true
1941
1946
  this.inputText = nextText
1942
1947
 
@@ -1946,6 +1951,9 @@ export default {
1946
1951
  if (submit && nextText.trim()) {
1947
1952
  await this.onSubmitInput()
1948
1953
  }
1954
+ this.$nextTick(() => {
1955
+ this.suppressAutoRestore = false
1956
+ })
1949
1957
  })
1950
1958
  },
1951
1959
 
@@ -9,8 +9,19 @@
9
9
  :action-items="actionItems"
10
10
  :menu-open="menuOpen"
11
11
  :search-open="searchOpen"
12
+ :search-request="homeSearchRequest"
13
+ :pages="searchPages"
14
+ :files="searchFiles"
15
+ :groups="searchGroups"
16
+ :include-district-results="includeDistrictResults"
17
+ :search-language-code="searchLanguageCode"
18
+ :query-pages-handler="queryPagesHandler"
19
+ :query-files-handler="queryFilesHandler"
20
+ :query-groups-handler="queryGroupsHandler"
21
+ :query-ai-site-handler="queryAiSiteHandler"
12
22
  :organization="organization"
13
23
  :organization-options="organizationOptions"
24
+ :ai-search-feedback-form-url="aiSearchFeedbackFormUrl"
14
25
  @update:menuOpen="$emit('update:menuOpen', $event)"
15
26
  @update:searchOpen="$emit('update:searchOpen', $event)"
16
27
  >
@@ -63,6 +74,7 @@
63
74
  :organization="organization"
64
75
  :organization-options="organizationOptions"
65
76
  :ai-search-feedback-form-url="aiSearchFeedbackFormUrl"
77
+ @submit-search="onHomeSearchSubmit"
66
78
  />
67
79
  </client-only>
68
80
 
@@ -218,6 +230,7 @@ export default {
218
230
  aiSearchFeedbackFormUrl: { type: String, default: '' },
219
231
  },
220
232
  data: () => ({
233
+ homeSearchRequest: null,
221
234
  factoids: [
222
235
  {
223
236
  start: 0,
@@ -249,6 +262,14 @@ export default {
249
262
  },
250
263
  ],
251
264
  }),
265
+ methods: {
266
+ onHomeSearchSubmit(request) {
267
+ this.homeSearchRequest = {
268
+ ...request,
269
+ requestedAt: Date.now(),
270
+ }
271
+ },
272
+ },
252
273
  }
253
274
  </script>
254
275
 
@@ -9,8 +9,19 @@
9
9
  :action-items="actionItems"
10
10
  :menu-open="menuOpen"
11
11
  :search-open="searchOpen"
12
+ :search-request="homeSearchRequest"
13
+ :pages="searchPages"
14
+ :files="searchFiles"
15
+ :groups="searchGroups"
16
+ :include-district-results="includeDistrictResults"
17
+ :search-language-code="searchLanguageCode"
18
+ :query-pages-handler="queryPagesHandler"
19
+ :query-files-handler="queryFilesHandler"
20
+ :query-groups-handler="queryGroupsHandler"
21
+ :query-ai-site-handler="queryAiSiteHandler"
12
22
  :organization-options="organizationOptions"
13
23
  :organization="organization"
24
+ :ai-search-feedback-form-url="aiSearchFeedbackFormUrl"
14
25
  @update:menuOpen="$emit('update:menuOpen', $event)"
15
26
  @update:searchOpen="$emit('update:searchOpen', $event)"
16
27
  />
@@ -107,6 +118,7 @@
107
118
  :organization="organization"
108
119
  :organization-options="organizationOptions"
109
120
  :ai-search-feedback-form-url="aiSearchFeedbackFormUrl"
121
+ @submit-search="onHomeSearchSubmit"
110
122
  />
111
123
  </client-only>
112
124
  <Asd20BlockSchedule
@@ -233,6 +245,7 @@ export default {
233
245
 
234
246
  data: () => ({
235
247
  showLogo: true,
248
+ homeSearchRequest: null,
236
249
  }),
237
250
 
238
251
  computed: {
@@ -316,6 +329,12 @@ export default {
316
329
  download: event.currentTarget?.hasAttribute?.('download'),
317
330
  })
318
331
  },
332
+ onHomeSearchSubmit(request) {
333
+ this.homeSearchRequest = {
334
+ ...request,
335
+ requestedAt: Date.now(),
336
+ }
337
+ },
319
338
  },
320
339
  }
321
340
  </script>
@@ -9,8 +9,19 @@
9
9
  :action-items="actionItems"
10
10
  :menu-open="menuOpen"
11
11
  :search-open="searchOpen"
12
+ :search-request="homeSearchRequest"
13
+ :pages="searchPages"
14
+ :files="searchFiles"
15
+ :groups="searchGroups"
16
+ :include-district-results="includeDistrictResults"
17
+ :search-language-code="searchLanguageCode"
18
+ :query-pages-handler="queryPagesHandler"
19
+ :query-files-handler="queryFilesHandler"
20
+ :query-groups-handler="queryGroupsHandler"
21
+ :query-ai-site-handler="queryAiSiteHandler"
12
22
  :organization="organization"
13
23
  :organization-options="organizationOptions"
24
+ :ai-search-feedback-form-url="aiSearchFeedbackFormUrl"
14
25
  @update:menuOpen="$emit('update:menuOpen', $event)"
15
26
  @update:searchOpen="$emit('update:searchOpen', $event)"
16
27
  />
@@ -108,6 +119,7 @@
108
119
  :organization="organization"
109
120
  :organization-options="organizationOptions"
110
121
  :ai-search-feedback-form-url="aiSearchFeedbackFormUrl"
122
+ @submit-search="onHomeSearchSubmit"
111
123
  />
112
124
  </client-only>
113
125
 
@@ -235,6 +247,9 @@ export default {
235
247
  queryAiSiteHandler: { type: Function, default: null },
236
248
  aiSearchFeedbackFormUrl: { type: String, default: '' },
237
249
  },
250
+ data: () => ({
251
+ homeSearchRequest: null,
252
+ }),
238
253
  computed: {
239
254
  blockEvent() {
240
255
  if (!this.events || this.events.length === 0) return null
@@ -269,6 +284,12 @@ export default {
269
284
  }
270
285
  },
271
286
  methods: {
287
+ onHomeSearchSubmit(request) {
288
+ this.homeSearchRequest = {
289
+ ...request,
290
+ requestedAt: Date.now(),
291
+ }
292
+ },
272
293
  onLogoClick(event, link) {
273
294
  navigateInternalHref(this, event, link, {
274
295
  target: event.currentTarget?.getAttribute?.('target'),