ajo-ui 0.1.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.
Files changed (103) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +126 -0
  3. package/dist/accordion.js +130 -0
  4. package/dist/avatar.js +54 -0
  5. package/dist/calendar.js +2 -0
  6. package/dist/carousel.js +239 -0
  7. package/dist/chart.js +790 -0
  8. package/dist/checkbox-group.js +70 -0
  9. package/dist/checkbox.js +77 -0
  10. package/dist/chunks/bar-CVafh6C1.js +99 -0
  11. package/dist/chunks/calendar-q8jZ8Cxr.js +1923 -0
  12. package/dist/chunks/collection-DtRB63U4.js +111 -0
  13. package/dist/chunks/data-table-DpkWv4y4.js +1039 -0
  14. package/dist/chunks/menu-B45IyHHC.js +704 -0
  15. package/dist/chunks/native-BJdhd9XJ.js +20 -0
  16. package/dist/chunks/popup-C8Bb3l_g.js +459 -0
  17. package/dist/chunks/popup-surface-BDCgtVU0.js +18 -0
  18. package/dist/chunks/position-D6_i_SRn.js +434 -0
  19. package/dist/chunks/virtual-list-B7hjGkjk.js +413 -0
  20. package/dist/collapsible.js +106 -0
  21. package/dist/command.js +263 -0
  22. package/dist/context-menu.js +112 -0
  23. package/dist/data-table.js +5 -0
  24. package/dist/dialog.js +207 -0
  25. package/dist/direction.js +22 -0
  26. package/dist/drawer.js +139 -0
  27. package/dist/field.js +26 -0
  28. package/dist/index.js +38 -0
  29. package/dist/input-date.js +994 -0
  30. package/dist/input-group.js +52 -0
  31. package/dist/input-otp.js +179 -0
  32. package/dist/menu.js +2 -0
  33. package/dist/menubar.js +236 -0
  34. package/dist/message-scroller.js +446 -0
  35. package/dist/navigation-menu.js +330 -0
  36. package/dist/popover.js +307 -0
  37. package/dist/progress.js +39 -0
  38. package/dist/radio-group.js +107 -0
  39. package/dist/resizable.js +172 -0
  40. package/dist/select.js +961 -0
  41. package/dist/sidebar.js +343 -0
  42. package/dist/slider.js +259 -0
  43. package/dist/switch.js +53 -0
  44. package/dist/tabs.js +182 -0
  45. package/dist/toast.js +492 -0
  46. package/dist/toggle-group.js +111 -0
  47. package/dist/toggle.js +52 -0
  48. package/dist/toolbar.js +127 -0
  49. package/dist/tooltip.js +196 -0
  50. package/dist/utils.js +104 -0
  51. package/dist/virtual-list.js +2 -0
  52. package/package.json +250 -0
  53. package/src/accordion.tsx +261 -0
  54. package/src/availability.ts +261 -0
  55. package/src/avatar.tsx +99 -0
  56. package/src/bar.ts +156 -0
  57. package/src/calendar.tsx +1441 -0
  58. package/src/carousel.tsx +424 -0
  59. package/src/chart.tsx +1194 -0
  60. package/src/checkbox-group.tsx +132 -0
  61. package/src/checkbox.tsx +130 -0
  62. package/src/collapsible.tsx +188 -0
  63. package/src/collection.ts +154 -0
  64. package/src/command.tsx +511 -0
  65. package/src/context-menu.tsx +233 -0
  66. package/src/data-table-contract.ts +143 -0
  67. package/src/data-table-model.ts +760 -0
  68. package/src/data-table.tsx +475 -0
  69. package/src/dialog.tsx +393 -0
  70. package/src/direction.tsx +45 -0
  71. package/src/drawer.tsx +251 -0
  72. package/src/field.tsx +61 -0
  73. package/src/index.ts +37 -0
  74. package/src/input-date.tsx +1539 -0
  75. package/src/input-group.tsx +142 -0
  76. package/src/input-otp.tsx +324 -0
  77. package/src/menu-cluster.ts +124 -0
  78. package/src/menu.tsx +1095 -0
  79. package/src/menubar.tsx +459 -0
  80. package/src/message-scroller.tsx +732 -0
  81. package/src/native.ts +26 -0
  82. package/src/navigation-menu.tsx +578 -0
  83. package/src/popover.tsx +519 -0
  84. package/src/popup-surface.tsx +31 -0
  85. package/src/popup.ts +569 -0
  86. package/src/position.ts +523 -0
  87. package/src/progress.tsx +70 -0
  88. package/src/radio-group.tsx +186 -0
  89. package/src/resizable.tsx +310 -0
  90. package/src/segments.ts +922 -0
  91. package/src/select.tsx +1501 -0
  92. package/src/sidebar.tsx +683 -0
  93. package/src/slider.tsx +424 -0
  94. package/src/switch.tsx +104 -0
  95. package/src/tabs.tsx +314 -0
  96. package/src/toast.tsx +923 -0
  97. package/src/toggle-group.tsx +249 -0
  98. package/src/toggle.tsx +91 -0
  99. package/src/toolbar.tsx +212 -0
  100. package/src/tooltip.tsx +359 -0
  101. package/src/utils.ts +204 -0
  102. package/src/virtual-list.tsx +205 -0
  103. package/src/virtual.ts +385 -0
package/src/popup.ts ADDED
@@ -0,0 +1,569 @@
1
+ import type { Host } from 'ajo-cloves'
2
+ import { callRef, controlled, dismiss, dom, hover, id, resize } from 'ajo-cloves'
3
+ import { closePopover, openPopover, popoverOpen } from './native'
4
+ import { position, type PositionProfile, type PositionReference } from './position'
5
+ import { popupStyle, type PopupPosition } from './utils'
6
+
7
+ type ContentAttrsOptions<Element extends HTMLElement> = {
8
+ id?: unknown
9
+ open?: boolean
10
+ ref?: unknown
11
+ setContent?: (element: Element | null) => void
12
+ style?: unknown
13
+ tabindex?: number | string
14
+ }
15
+
16
+ /** Builds native/manual popup attrs and a composed content ref. */
17
+ export const contentAttrs = <Element extends HTMLElement>(options: ContentAttrsOptions<Element>): Record<string, unknown> => {
18
+ const { id, open, ref, setContent, style, tabindex } = options
19
+ const attrs: Record<string, unknown> = {
20
+ popover: 'manual',
21
+ style: typeof style === 'string' ? style : popupStyle(),
22
+ }
23
+ if ('id' in options) attrs.id = id
24
+ if ('open' in options) attrs['data-state'] = open ? 'open' : 'closed'
25
+ if ('tabindex' in options) attrs.tabindex = tabindex
26
+ if ('ref' in options || 'setContent' in options) {
27
+ attrs.ref = (element: Element | null) => {
28
+ setContent?.(element)
29
+ callRef(ref, element)
30
+ }
31
+ }
32
+ return attrs
33
+ }
34
+
35
+ export type PopupView<Trigger extends HTMLElement = HTMLElement, Content extends HTMLElement = HTMLDivElement> = {
36
+ readonly open: boolean
37
+ readonly trigger: Trigger | null
38
+ readonly content: Content | null
39
+ readonly reference: PositionReference | null
40
+ readonly triggerId: string
41
+ readonly contentId: string
42
+ /** Adopts the trigger's rendered id synchronously, or restores the generated id. */
43
+ adoptTriggerId(id?: unknown): string
44
+ /** Composes caller declarations with the live styles owned by popup positioning. */
45
+ contentStyle(style?: unknown): string
46
+ /** Owns the internal arrow probe's stable ref and live positioning style. */
47
+ arrowAttrs(): {
48
+ ref: (element: HTMLElement | null) => void
49
+ style: string
50
+ }
51
+ /** Reads controlled state and current root positioning once per render. */
52
+ sync(open: boolean | null | undefined, position?: PopupPosition): boolean
53
+ setOpen(open: boolean, event?: Event): void
54
+ /** Seeds uncontrolled state without notifying onOpenChange. */
55
+ init(open: boolean): void
56
+ close(event?: Event): void
57
+ hold(zone: string, event: Event): void
58
+ release(zone: string, event: Event): void
59
+ cancelHover(): void
60
+ setTrigger(element: Trigger | null): void
61
+ setContent(element: Content | null): void
62
+ setReference(element: PositionReference | null): void
63
+ /** Requests a coalesced geometry update for a proven manual case. */
64
+ update(): void
65
+ }
66
+
67
+ export type PopupOptions<View> = {
68
+ prefix: string
69
+ profile: Exclude<PositionProfile, 'chart'>
70
+ initialOpen: boolean
71
+ disabled?: () => boolean
72
+ hover?: {
73
+ openDelay: () => number
74
+ closeDelay: () => number
75
+ }
76
+ onOpenChange?: (open: boolean, event?: Event) => void
77
+ /** Runs after current first geometry commit or after synchronous close. */
78
+ onSync?: (open: boolean, view: View) => void
79
+ reference?: (view: View) => PositionReference | null
80
+ source?: (view: View) => HTMLElement | null
81
+ boundary?: (view: View) => Element | null
82
+ /** Optional parent clip used only by reference-hidden detection. */
83
+ referenceBoundary?: (view: View) => Element | null
84
+ referenceHidden?: 'close' | 'hide' | 'none'
85
+ /** Refreshes native source identity when an open reference tuple changes. */
86
+ reopenOnReferenceChange?: boolean
87
+ /** Fires after a current explicit geometry commit and the surface is revealed. */
88
+ onPosition?: (view: View) => void
89
+ dismiss?: {
90
+ prevent?: boolean
91
+ escape?: false | 'host' | 'document'
92
+ outside?: boolean
93
+ inside?: (view: View) => (Element | null | undefined)[]
94
+ onDismiss?: (event: Event, view: View) => void
95
+ }
96
+ }
97
+
98
+ const stacks = new WeakMap<Document, object[]>()
99
+ const handled = new WeakSet<Event>()
100
+
101
+ const declaration = (name: string, value: string, priority = '') =>
102
+ value ? `${name}:${value}${priority ? '!important' : ''}` : ''
103
+
104
+ const contentOwnedStyle = (element: HTMLElement, arrow: boolean) => [
105
+ declaration('position', element.style.position),
106
+ declaration('left', element.style.left),
107
+ declaration('top', element.style.top),
108
+ declaration('transform-origin', element.style.transformOrigin),
109
+ declaration('box-sizing', element.style.boxSizing),
110
+ declaration('max-width', element.style.maxWidth),
111
+ declaration('max-height', element.style.maxHeight),
112
+ declaration('visibility', element.style.visibility, element.style.getPropertyPriority('visibility')),
113
+ declaration('pointer-events', element.style.pointerEvents, element.style.getPropertyPriority('pointer-events')),
114
+ declaration('--reference-width', element.style.getPropertyValue('--reference-width')),
115
+ declaration('--reference-height', element.style.getPropertyValue('--reference-height')),
116
+ declaration('--available-width', element.style.getPropertyValue('--available-width')),
117
+ declaration('--available-height', element.style.getPropertyValue('--available-height')),
118
+ declaration('--popup-arrow-center', element.style.getPropertyValue('--popup-arrow-center')),
119
+ arrow ? 'overflow:visible' : '',
120
+ ].filter(Boolean).join(';')
121
+
122
+ const arrowOwnedStyle = (element: HTMLElement | null) => element ? [
123
+ declaration('left', element.style.left),
124
+ declaration('right', element.style.right),
125
+ declaration('top', element.style.top),
126
+ declaration('bottom', element.style.bottom),
127
+ ].filter(Boolean).join(';') : ''
128
+
129
+ const stack = (element: HTMLElement) => {
130
+ const document = element.ownerDocument
131
+ let current = stacks.get(document)
132
+ if (!current) stacks.set(document, current = [])
133
+ return current
134
+ }
135
+
136
+ const remove = (view: object, element: HTMLElement | null) => {
137
+ if (!element) return
138
+ const current = stack(element)
139
+ const index = current.indexOf(view)
140
+ if (index >= 0) current.splice(index, 1)
141
+ }
142
+
143
+ const push = (view: object, element: HTMLElement) => {
144
+ const current = stack(element)
145
+ const index = current.indexOf(view)
146
+ if (index >= 0) current.splice(index, 1)
147
+ current.push(view)
148
+ }
149
+
150
+ const top = (view: object, element: HTMLElement | null) => {
151
+ if (!element) return false
152
+ const current = stack(element)
153
+ return current[current.length - 1] === view
154
+ }
155
+
156
+ /** Private popup Module: Ajo interaction/native lifecycle over the position Adapter. */
157
+ export const popup = <
158
+ Trigger extends HTMLElement = HTMLElement,
159
+ Content extends HTMLElement = HTMLDivElement,
160
+ View extends PopupView<Trigger, Content> = PopupView<Trigger, Content>,
161
+ >(host: Host, options: PopupOptions<View>): View => {
162
+ const rootId = id(options.prefix)
163
+ const contentId = `${rootId}-content`
164
+ const state = controlled<boolean>(host, {
165
+ fallback: options.initialOpen,
166
+ onChange: options.onOpenChange,
167
+ })
168
+ let arrow: HTMLElement | null = null
169
+ let content: Content | null = null
170
+ let contentInputStyle: unknown
171
+ let opened = state.value
172
+ let overflowElement: Content | null = null
173
+ let preferred: PopupPosition = {}
174
+ let referenceElement: PositionReference | null = null
175
+ let referenceIsHidden = false
176
+ let shown = false
177
+ let trigger: Trigger | null = null
178
+ const generatedTriggerId = `${rootId}-trigger`
179
+ let triggerId = generatedTriggerId
180
+ let version = 0
181
+ let view: View
182
+ let opening = false
183
+ let positionTask: Promise<boolean> | undefined
184
+ let reopen = false
185
+ let scheduled = false
186
+ const inputStyle = dom(host) ? host.ownerDocument.createElement('span').style : null
187
+ let inputPointerEvents = { priority: '', value: '' }
188
+ let inputVisibility = { priority: '', value: '' }
189
+
190
+ const rememberContentStyle = (style: unknown) => {
191
+ contentInputStyle = style
192
+ if (!inputStyle) return
193
+ inputStyle.cssText = typeof style === 'string' ? style : ''
194
+ inputPointerEvents = {
195
+ priority: inputStyle.getPropertyPriority('pointer-events'),
196
+ value: inputStyle.getPropertyValue('pointer-events'),
197
+ }
198
+ inputVisibility = {
199
+ priority: inputStyle.getPropertyPriority('visibility'),
200
+ value: inputStyle.getPropertyValue('visibility'),
201
+ }
202
+ }
203
+ const restore = (target: Content, name: string, input: { priority: string; value: string }) => {
204
+ if (input.value) target.style.setProperty(name, input.value, input.priority)
205
+ else target.style.removeProperty(name)
206
+ }
207
+ const conceal = (target: Content, hidden: boolean) => {
208
+ if (hidden) {
209
+ target.style.setProperty('visibility', 'hidden', 'important')
210
+ target.style.setProperty('pointer-events', 'none', 'important')
211
+ } else {
212
+ restore(target, 'visibility', inputVisibility)
213
+ restore(target, 'pointer-events', inputPointerEvents)
214
+ }
215
+ }
216
+
217
+ const reference = () => options.reference?.(view) ?? referenceElement ?? trigger
218
+ const connectedSource = (element: HTMLElement | null | undefined) => element?.isConnected ? element : null
219
+ const source = () => connectedSource(options.source?.(view)) ?? connectedSource(trigger)
220
+ const clearOverflow = () => {
221
+ if (!overflowElement) return
222
+ const target = overflowElement
223
+ overflowElement = null
224
+ if (target === content) {
225
+ target.setAttribute('style', popupStyle(contentInputStyle, contentOwnedStyle(target, false)))
226
+ } else {
227
+ target.style.overflow = ''
228
+ }
229
+ }
230
+ const syncOverflow = () => {
231
+ if (overflowElement && (overflowElement !== content || !arrow)) clearOverflow()
232
+ if (!content || !arrow) return
233
+ overflowElement = content
234
+ content.style.overflow = 'visible'
235
+ }
236
+ const reveal = (target: Content) => {
237
+ conceal(target, options.referenceHidden === 'hide' && referenceIsHidden)
238
+ }
239
+
240
+ const geometry = position(host, {
241
+ profile: options.profile,
242
+ elements: () => ({ reference: reference(), floating: content, arrow }),
243
+ placement: () => preferred.placement,
244
+ gap: () => preferred.gap,
245
+ boundary: () => options.boundary?.(view) ?? null,
246
+ referenceBoundary: () => options.referenceBoundary?.(view) ?? null,
247
+ referenceHidden(hidden) {
248
+ referenceIsHidden = hidden
249
+ if (!content) return
250
+ if (options.referenceHidden === 'hide') {
251
+ if (hidden || shown) conceal(content, hidden)
252
+ } else if (hidden && options.referenceHidden === 'close') {
253
+ view.close()
254
+ }
255
+ },
256
+ })
257
+
258
+ const intent = options.hover ? hover(host, {
259
+ openDelay: options.hover.openDelay,
260
+ closeDelay: options.hover.closeDelay,
261
+ onChange: (next, event) => setOpen(next, event),
262
+ }) : undefined
263
+
264
+ const closeTarget = (target: Content) => {
265
+ let error: unknown
266
+ try {
267
+ if (!closePopover(target)) error = new Error('Failed to close the native popover')
268
+ } catch (cause) {
269
+ error = cause
270
+ } finally {
271
+ conceal(target, false)
272
+ }
273
+ return error
274
+ }
275
+
276
+ const discard = (target: Content) => {
277
+ remove(view, target)
278
+ target.dataset.state = 'closed'
279
+ const error = closeTarget(target)
280
+ if (error !== undefined && !host.signal.aborted) host.throw(error)
281
+ }
282
+
283
+ const closeCurrent = (target = content, notify = true) => {
284
+ const wasShown = shown
285
+ if (target) target.dataset.state = 'closed'
286
+ version++
287
+ geometry.stop()
288
+ remove(view, target)
289
+ shown = false
290
+ positionTask = undefined
291
+ referenceIsHidden = false
292
+ if (!target) return
293
+ const error = closeTarget(target)
294
+ if (wasShown && notify) options.onSync?.(false, view)
295
+ if (error !== undefined && !host.signal.aborted) host.throw(error)
296
+ }
297
+
298
+ const openCurrent = async () => {
299
+ const target = content
300
+ const currentReference = reference()
301
+ if (!target || !opened || host.signal.aborted) return false
302
+ if (!currentReference) {
303
+ view.close()
304
+ return false
305
+ }
306
+ const wasShown = shown
307
+ const wasNativeOpen = popoverOpen(target)
308
+ const token = ++version
309
+ if (!wasShown) target.dataset.state = 'closed'
310
+ conceal(target, true)
311
+
312
+ try {
313
+ if (!openPopover(target, source())) {
314
+ throw new Error('Failed to open the native popover')
315
+ }
316
+ if (!wasNativeOpen) push(view, target)
317
+ const committed = await geometry.start()
318
+ if (
319
+ !committed ||
320
+ token !== version ||
321
+ !opened ||
322
+ content !== target ||
323
+ !popoverOpen(target)
324
+ ) {
325
+ geometry.stop()
326
+ if (reopen && opened && !host.signal.aborted) {
327
+ if (content === target) conceal(target, true)
328
+ else discard(target)
329
+ } else if (opened && content === target && !host.signal.aborted) {
330
+ view.close()
331
+ } else if (content === target) {
332
+ closeCurrent(target)
333
+ } else {
334
+ discard(target)
335
+ }
336
+ return false
337
+ }
338
+
339
+ shown = true
340
+ target.dataset.state = 'open'
341
+ reveal(target)
342
+ options.onPosition?.(view)
343
+ if (!wasShown) options.onSync?.(true, view)
344
+ return true
345
+ } catch (error) {
346
+ geometry.stop()
347
+ if (opened && content === target && !host.signal.aborted) view.close()
348
+ else if (content === target) closeCurrent(target)
349
+ else discard(target)
350
+ throw error
351
+ }
352
+ }
353
+
354
+ const run = async () => {
355
+ scheduled = false
356
+ if (host.signal.aborted) {
357
+ reopen = false
358
+ return
359
+ }
360
+ if (opening) return
361
+ opening = true
362
+ try {
363
+ do {
364
+ reopen = false
365
+ if (opened) await openCurrent()
366
+ else closeCurrent()
367
+ } while (reopen)
368
+ } finally {
369
+ opening = false
370
+ if (reopen) schedule()
371
+ }
372
+ }
373
+
374
+ function schedule() {
375
+ if (host.signal.aborted) return
376
+ reopen = true
377
+ if (scheduled || opening) return
378
+ scheduled = true
379
+ queueMicrotask(() => {
380
+ if (host.signal.aborted) {
381
+ scheduled = false
382
+ reopen = false
383
+ return
384
+ }
385
+ void run().catch(report)
386
+ })
387
+ }
388
+
389
+ const report = (error: unknown) => {
390
+ if (host.signal.aborted) return
391
+ queueMicrotask(() => {
392
+ if (!host.signal.aborted) host.throw(error)
393
+ })
394
+ }
395
+
396
+ const update = () => {
397
+ if (!opened || host.signal.aborted) return
398
+ if (!shown && !opening) {
399
+ schedule()
400
+ return
401
+ }
402
+ const target = content
403
+ const pending = geometry.update()
404
+ if (opening || pending === positionTask) return
405
+ positionTask = pending
406
+ const clear = () => {
407
+ if (positionTask === pending) positionTask = undefined
408
+ }
409
+ void pending.then(committed => {
410
+ clear()
411
+ if (committed && opened && shown && target === content) options.onPosition?.(view)
412
+ }, error => {
413
+ clear()
414
+ report(error)
415
+ })
416
+ }
417
+
418
+ const arrowSize = resize(host, {
419
+ target: () => opened ? arrow : null,
420
+ onResize: update,
421
+ })
422
+
423
+ const restart = (reopenSource = false) => {
424
+ if (!opened || host.signal.aborted) return
425
+ version++
426
+ geometry.stop()
427
+ positionTask = undefined
428
+ referenceIsHidden = false
429
+ if (reopenSource && content && popoverOpen(content)) {
430
+ const error = closeTarget(content)
431
+ if (error !== undefined) report(error)
432
+ }
433
+ if (content) {
434
+ conceal(content, true)
435
+ }
436
+ schedule()
437
+ }
438
+
439
+ const setOpen = (next: boolean, event?: Event) => {
440
+ if (host.signal.aborted) return
441
+ if (options.disabled?.() && next) return
442
+ if (next === opened) return
443
+ state.set(next, event)
444
+ opened = state.value
445
+ intent?.sync(opened)
446
+ arrowSize.sync()
447
+ if (opened) schedule()
448
+ else closeCurrent()
449
+ }
450
+
451
+ view = {
452
+ get open() { return opened },
453
+ get trigger() { return trigger },
454
+ get content() { return content },
455
+ get reference() { return referenceElement },
456
+ get triggerId() { return triggerId },
457
+ get contentId() { return contentId },
458
+ adoptTriggerId(id) {
459
+ triggerId = typeof id === 'string' && id ? id : generatedTriggerId
460
+ return triggerId
461
+ },
462
+ contentStyle(style) {
463
+ rememberContentStyle(style)
464
+ return popupStyle(style, content ? contentOwnedStyle(content, Boolean(arrow)) : '')
465
+ },
466
+ arrowAttrs() {
467
+ return {
468
+ ref: setArrow,
469
+ style: arrowOwnedStyle(arrow),
470
+ }
471
+ },
472
+ sync(open, next = {}) {
473
+ const changed = preferred.placement !== next.placement || preferred.gap !== next.gap
474
+ preferred = next
475
+ const previous = opened
476
+ opened = state.sync(open ?? undefined)
477
+ intent?.sync(opened)
478
+ arrowSize.sync()
479
+ if (!opened) {
480
+ if (previous || shown) closeCurrent()
481
+ } else if (!shown && !opening) {
482
+ schedule()
483
+ } else if (changed) {
484
+ update()
485
+ }
486
+ return opened
487
+ },
488
+ setOpen,
489
+ init(open) {
490
+ if (state.controlled || open === opened) return
491
+ state.init(open)
492
+ opened = open
493
+ intent?.sync(opened)
494
+ arrowSize.sync()
495
+ if (opened) schedule()
496
+ else closeCurrent()
497
+ },
498
+ close: (event?: Event) => setOpen(false, event),
499
+ hold(zone, event) {
500
+ if (!options.disabled?.()) intent?.hold(zone, event)
501
+ },
502
+ release: (zone, event) => intent?.release(zone, event),
503
+ cancelHover: () => intent?.cancel(),
504
+ setTrigger(element) {
505
+ if (element === trigger) return
506
+ const previousReference = reference()
507
+ const previousSource = source()
508
+ trigger = element
509
+ if (element?.id && element.id !== triggerId) {
510
+ triggerId = element.id
511
+ queueMicrotask(() => host.next())
512
+ }
513
+ const referenceChanged = reference() !== previousReference
514
+ const sourceChanged = source() !== previousSource
515
+ if (referenceChanged || sourceChanged) restart(sourceChanged)
516
+ },
517
+ setContent(element) {
518
+ if (element === content) return
519
+ const previous = content
520
+ if (previous) closeCurrent(previous, false)
521
+ content = element
522
+ syncOverflow()
523
+ if (element) element.dataset.state = 'closed'
524
+ if (opened) schedule()
525
+ },
526
+ setReference(element) {
527
+ if (element === referenceElement) return
528
+ const previousReference = reference()
529
+ const previousSource = source()
530
+ referenceElement = element
531
+ const referenceChanged = reference() !== previousReference
532
+ const sourceChanged = source() !== previousSource
533
+ if (referenceChanged || sourceChanged) restart(sourceChanged || options.reopenOnReferenceChange)
534
+ },
535
+ update,
536
+ } as View
537
+
538
+ function setArrow(element: HTMLElement | null) {
539
+ if (element === arrow) return
540
+ arrow = element
541
+ syncOverflow()
542
+ arrowSize.sync()
543
+ restart()
544
+ }
545
+
546
+ if (options.dismiss) dismiss(host, {
547
+ active: () => opened && Boolean(content && popoverOpen(content)) && top(view, content),
548
+ inside: () => options.dismiss?.inside?.(view) ?? [trigger, content],
549
+ prevent: options.dismiss.prevent,
550
+ escape: options.dismiss.escape ?? 'document',
551
+ outside: options.dismiss.outside,
552
+ onDismiss(event) {
553
+ if (handled.has(event)) return
554
+ handled.add(event)
555
+ if (options.dismiss?.onDismiss) options.dismiss.onDismiss(event, view)
556
+ else view.close(event)
557
+ },
558
+ })
559
+
560
+ host.signal.addEventListener('abort', () => {
561
+ opened = false
562
+ reopen = false
563
+ scheduled = false
564
+ intent?.sync(false)
565
+ closeCurrent(content, false)
566
+ clearOverflow()
567
+ }, { once: true })
568
+ return view
569
+ }