@operato/popup 10.29.49 → 10.29.52

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.
@@ -0,0 +1,71 @@
1
+ import { expect, fixture } from '@open-wc/testing'
2
+ import { html } from 'lit'
3
+
4
+ import '../src/ox-popup-list.js'
5
+ import type { OxPopupList } from '../src/ox-popup-list.js'
6
+
7
+ /**
8
+ * A searchable list is a select with a search box on top. It opened with the focus on an option, so the user had to
9
+ * click the box before typing, and Enter in the box did nothing.
10
+ */
11
+ describe('ox-popup-list with search', () => {
12
+ const tick = () => new Promise(resolve => setTimeout(resolve, 10))
13
+
14
+ async function opened() {
15
+ const list = await fixture<OxPopupList>(html`<ox-popup-list with-search>
16
+ <div option value="a">Alpha</div>
17
+ <div option value="m1">MES one</div>
18
+ <div option value="m2">MES two</div>
19
+ </ox-popup-list>`)
20
+ list.open({ left: 0, top: 0 })
21
+ await list.updateComplete
22
+ await tick()
23
+ return list
24
+ }
25
+
26
+ const type = async (list: OxPopupList, text: string) => {
27
+ list.searchInput.value = text
28
+ list.searchInput.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }))
29
+ await list.updateComplete
30
+ }
31
+
32
+ const enter = (list: OxPopupList, init: KeyboardEventInit = {}) =>
33
+ list.searchInput.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, composed: true, ...init }))
34
+
35
+ it('opens with the focus in the search box', async () => {
36
+ const list = await opened()
37
+ /* Compared as a boolean: a failed chai match on an element hangs the runner. */
38
+ expect(list.shadowRoot!.activeElement === list.searchInput).to.equal(true)
39
+ })
40
+
41
+ it('picks the first match on Enter in the search box', async () => {
42
+ const list = await opened()
43
+ await type(list, 'mes')
44
+ let picked: unknown
45
+ list.addEventListener('select', (e: Event) => (picked = (e as CustomEvent).detail))
46
+ enter(list)
47
+ await tick()
48
+ expect(picked).to.equal('m1')
49
+ })
50
+
51
+ it('leaves Enter alone while a word is being composed', async () => {
52
+ const list = await opened()
53
+ await type(list, 'mes')
54
+ let picked = false
55
+ list.addEventListener('select', () => (picked = true))
56
+ enter(list, { isComposing: true })
57
+ await tick()
58
+ expect(picked).to.equal(false)
59
+ })
60
+
61
+ it('clears the last search when it opens again', async () => {
62
+ const list = await opened()
63
+ await type(list, 'mes')
64
+ list.close()
65
+ list.open({ left: 0, top: 0 })
66
+ await list.updateComplete
67
+ await tick()
68
+ expect(list.searchInput.value).to.equal('')
69
+ expect(list.querySelectorAll('[option]:not([hidden])').length).to.equal(3)
70
+ })
71
+ })
@@ -0,0 +1,81 @@
1
+ import { expect } from '@open-wc/testing'
2
+ import { html, render } from 'lit'
3
+
4
+ import '../src/ox-popup-list.js'
5
+ import type { OxPopupList } from '../src/ox-popup-list.js'
6
+
7
+ /*
8
+ * A list taller than the room below its select was moved up over the select itself (2026-10-25). In a grid cell the
9
+ * second press of a double-click then landed on an option and picked it. A popup stays off the line it opens from: it
10
+ * shrinks to the room below, or opens above when there is more room there.
11
+ */
12
+ describe('ox-popup placement', () => {
13
+ const frame = () => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))
14
+ let list: OxPopupList
15
+
16
+ async function open(count: number, place: { top: number; anchorTop?: number }) {
17
+ const host = document.createElement('div')
18
+ document.body.appendChild(host)
19
+ render(
20
+ html`<ox-popup-list with-search>
21
+ ${Array.from({ length: count }, (_, i) => html`<div option value=${i}>Option ${i}</div>`)}
22
+ </ox-popup-list>`,
23
+ host
24
+ )
25
+ list = host.querySelector('ox-popup-list') as OxPopupList
26
+ document.body.appendChild(list)
27
+ host.remove()
28
+ await list.updateComplete
29
+ list.open({ left: 20, ...place })
30
+ await list.updateComplete
31
+ await frame()
32
+ return list.getBoundingClientRect()
33
+ }
34
+
35
+ afterEach(() => {
36
+ list?.close()
37
+ list?.remove()
38
+ })
39
+
40
+ it('a long list under a select with more room below shrinks to that room and keeps its search box', async () => {
41
+ const anchorTop = Math.round(window.innerHeight / 3)
42
+ const box = await open(200, { anchorTop, top: anchorTop + 30 })
43
+ expect(box.top).to.be.at.least(anchorTop + 30 - 0.5)
44
+ expect(box.bottom).to.be.at.most(window.innerHeight + 0.5)
45
+ const search = list.shadowRoot!.querySelector('[search]')!.getBoundingClientRect()
46
+ expect(search.top).to.be.at.least(anchorTop + 30 - 0.5)
47
+ const last = list.querySelector('[option]:last-of-type')!
48
+ expect(last.getBoundingClientRect().bottom > box.bottom).to.equal(true)
49
+ })
50
+
51
+ it('a long list under a select near the bottom opens above the select', async () => {
52
+ const anchorTop = window.innerHeight - 60
53
+ const box = await open(200, { anchorTop, top: anchorTop + 30 })
54
+ expect(box.bottom).to.be.at.most(anchorTop + 0.5)
55
+ expect(box.top).to.be.at.least(-0.5)
56
+ })
57
+
58
+ it('a short list that fits stays where it was asked to open', async () => {
59
+ const box = await open(3, { anchorTop: 40, top: 70 })
60
+ expect(box.top).to.be.closeTo(70, 0.5)
61
+ })
62
+
63
+ it('a menu at a point near the bottom opens above the point, not over it', async () => {
64
+ const point = window.innerHeight - 20
65
+ const box = await open(30, { top: point })
66
+ expect(box.bottom).to.be.at.most(point + 0.5)
67
+ })
68
+
69
+ it('opened again elsewhere, it does not keep the last place or the last cut', async () => {
70
+ const anchorTop = Math.round(window.innerHeight / 2)
71
+ await open(200, { anchorTop, top: anchorTop + 30 })
72
+ list.close()
73
+ list.open({ left: 20, anchorTop: 40, top: 70 })
74
+ await list.updateComplete
75
+ await frame()
76
+ const box = list.getBoundingClientRect()
77
+ expect(box.top).to.be.closeTo(70, 0.5)
78
+ /* The room below now, less the margin - not the shorter cut of the last opening. */
79
+ expect(box.bottom).to.be.closeTo(window.innerHeight - 10, 0.5)
80
+ })
81
+ })