dsh-wisp 1.31.0 → 1.33.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/README.md +69 -0
- package/lib/client.js +3309 -3109
- package/lib/client.template.js +3309 -3109
- package/package.json +1 -1
- package/verify-wisp.mjs +131 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-wisp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.33.0",
|
|
4
4
|
"description": "DeepSeek娘 — an unofficial floating desktop companion for the DeepSeek Harness UI. Zero dependencies; sprites inlined as data URIs; schedules on the Client timer service instead of the trapped browser timer globals.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
package/verify-wisp.mjs
CHANGED
|
@@ -2441,7 +2441,8 @@ if (clientSrc !== null) {
|
|
|
2441
2441
|
|
|
2442
2442
|
/* ---- 位置:2×2 的方位控件 ---- */
|
|
2443
2443
|
check(expand('位置'), 'the position group opens')
|
|
2444
|
-
|
|
2444
|
+
/* 角落按钮现在在「位置」的子面板里 —— 面板是菜单的兄弟节点,所以查询落在 layer 上 */
|
|
2445
|
+
const gridButtons = menuHost().querySelectorAll('.wisp-corner').filter((el) => el.removed !== true)
|
|
2445
2446
|
check(gridButtons.length === 4, 'the four corners are a 2×2 compass, not four text rows',
|
|
2446
2447
|
String(gridButtons.length) + ' 个')
|
|
2447
2448
|
/* 她此刻并不在任何角落(位置是拖出来的),所以先点一个角落归位,再看标记。 */
|
|
@@ -2449,10 +2450,135 @@ if (clientSrc !== null) {
|
|
|
2449
2450
|
toTopLeft.dispatch('click', itemEvent())
|
|
2450
2451
|
body5.dispatch('contextmenu', rightClickOnHer().ev)
|
|
2451
2452
|
check(expand('位置'), 'reopen the position group')
|
|
2452
|
-
const marked =
|
|
2453
|
+
const marked = menuHost().querySelectorAll('.wisp-corner')
|
|
2454
|
+
.filter((b) => b.removed !== true && b.dataset && b.dataset.current === 'true')
|
|
2453
2455
|
check(marked.length === 1 && marked[0].dataset.corner === '左上角',
|
|
2454
2456
|
'and the corner she is actually in is marked',
|
|
2455
|
-
|
|
2457
|
+
menuHost().querySelectorAll('.wisp-corner').filter((b) => b.removed !== true)
|
|
2458
|
+
.map((b) => b.textContent + (b.dataset.current ? '*' : '')).join(' '))
|
|
2459
|
+
|
|
2460
|
+
/* ---- 级联子菜单:悬停展开、移开收起 --------------------------------------
|
|
2461
|
+
这是**菜单栏**的形状:悬停一行,在它**旁边**开一个独立面板,而不是把下面的内容推下去。
|
|
2462
|
+
替身里没有真鼠标,但它有事件派发 + 虚拟时钟,所以状态机能被完整测到:
|
|
2463
|
+
派 pointerenter/pointerleave、推进时钟、看面板有没有出现、出现在哪一侧。
|
|
2464
|
+
测不到的是浏览器的命中测试本身("指针真的在这一行上")—— 那由冒烟里的真 Chromium 兜着。 */
|
|
2465
|
+
const flyoutOf = (name) => menuHost().querySelectorAll('.wisp-submenu')
|
|
2466
|
+
.find((el) => el.dataset && el.dataset.submenu === name && el.removed !== true) ?? null
|
|
2467
|
+
const rowOf = (name) => groupRow(name)
|
|
2468
|
+
const hoverIn = (name, pointerType) => rowOf(name).dispatch('pointerenter', { pointerType })
|
|
2469
|
+
const hoverOut = (name, pointerType) => rowOf(name).dispatch('pointerleave', { pointerType })
|
|
2470
|
+
const isOpen = (name) => {
|
|
2471
|
+
const row = rowOf(name)
|
|
2472
|
+
return row !== undefined && row.dataset.open === 'true'
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
groupRow('外观').dispatch('click', itemEvent()); groupRow('外观').dispatch('click', itemEvent())
|
|
2476
|
+
check(!isOpen('外观') && flyoutOf('外观') === null, 'no submenu is open at the start')
|
|
2477
|
+
|
|
2478
|
+
/* 悬停要**有延迟**:鼠标只是路过就弹出面板会抖 —— 这是 hover intent */
|
|
2479
|
+
hoverIn('外观', 'mouse')
|
|
2480
|
+
check(flyoutOf('外观') === null && !isOpen('外观'),
|
|
2481
|
+
'hovering does not pop a submenu instantly (a passing mouse would twitch it)')
|
|
2482
|
+
h.advance(200, 40)
|
|
2483
|
+
check(flyoutOf('外观') !== null && isOpen('外观'), 'after the intent delay the submenu appears')
|
|
2484
|
+
check(rowOf('外观').getAttribute('aria-haspopup') === 'true'
|
|
2485
|
+
&& rowOf('外观').getAttribute('aria-expanded') === 'true',
|
|
2486
|
+
'and the row says it has a submenu and that it is open',
|
|
2487
|
+
`haspopup=${rowOf('外观').getAttribute('aria-haspopup')} expanded=${rowOf('外观').getAttribute('aria-expanded')}`)
|
|
2488
|
+
|
|
2489
|
+
/* 关键:面板是**菜单的兄弟**(浮在旁边),不是菜单里把内容推下去的块 */
|
|
2490
|
+
const panel = flyoutOf('外观')
|
|
2491
|
+
check(panel.parentNode === menuHost(),
|
|
2492
|
+
'the submenu is a sibling of the menu, floating beside it')
|
|
2493
|
+
check(String(panel.style.left) !== '' && String(panel.style.top) !== '',
|
|
2494
|
+
'and it is positioned', `left=${panel.style.left} top=${panel.style.top}`)
|
|
2495
|
+
check(String(panel.style.left) !== String(menuOf().style.left),
|
|
2496
|
+
'not stacked on top of the menu', `panel=${panel.style.left} menu=${menuOf().style.left}`)
|
|
2497
|
+
|
|
2498
|
+
/* 移开:延迟后收起 */
|
|
2499
|
+
hoverOut('外观', 'mouse')
|
|
2500
|
+
h.advance(80, 40)
|
|
2501
|
+
check(flyoutOf('外观') !== null, 'leaving the row does not close it instantly')
|
|
2502
|
+
h.advance(400, 40)
|
|
2503
|
+
check(flyoutOf('外观') === null && !isOpen('外观'), 'after the grace period it closes')
|
|
2504
|
+
|
|
2505
|
+
/* 指针移进**面板**本身:算同一区域,不能关 */
|
|
2506
|
+
hoverIn('外观', 'mouse')
|
|
2507
|
+
h.advance(200, 40)
|
|
2508
|
+
const panel2 = flyoutOf('外观')
|
|
2509
|
+
/* 顺序要照真实浏览器:指针从行移到面板,先触发行的 leave、再触发面板的 enter。
|
|
2510
|
+
写反了的话"面板 enter 取消关闭"就被后面的 leave 又覆盖掉了。 */
|
|
2511
|
+
hoverOut('外观', 'mouse')
|
|
2512
|
+
panel2.dispatch('pointerenter', { pointerType: 'mouse' })
|
|
2513
|
+
h.advance(600, 40)
|
|
2514
|
+
check(flyoutOf('外观') !== null,
|
|
2515
|
+
'moving the pointer onto the panel keeps it open (row and panel are one region)')
|
|
2516
|
+
panel2.dispatch('pointerleave', { pointerType: 'mouse' })
|
|
2517
|
+
h.advance(600, 40)
|
|
2518
|
+
check(flyoutOf('外观') === null, 'leaving the panel closes it')
|
|
2519
|
+
|
|
2520
|
+
/* 路过不弹出 */
|
|
2521
|
+
hoverIn('行为', 'mouse')
|
|
2522
|
+
hoverOut('行为', 'mouse')
|
|
2523
|
+
h.advance(800, 40)
|
|
2524
|
+
check(flyoutOf('行为') === null, 'a mouse that just passes over never pops a submenu')
|
|
2525
|
+
|
|
2526
|
+
/* 触屏:不判 pointerType 的话,手指一碰就会弹面板 */
|
|
2527
|
+
hoverIn('外观', 'touch')
|
|
2528
|
+
h.advance(600, 40)
|
|
2529
|
+
check(flyoutOf('外观') === null, 'a touch pointer never opens a submenu by hovering')
|
|
2530
|
+
groupRow('外观').dispatch('click', itemEvent())
|
|
2531
|
+
check(flyoutOf('外观') !== null, 'but tapping the row still works')
|
|
2532
|
+
|
|
2533
|
+
/* 点击 = 钉住:移开也不关;再点一次收起 */
|
|
2534
|
+
hoverOut('外观', 'mouse')
|
|
2535
|
+
h.advance(800, 40)
|
|
2536
|
+
check(flyoutOf('外观') !== null, 'a submenu you clicked stays open when the mouse leaves')
|
|
2537
|
+
groupRow('外观').dispatch('click', itemEvent())
|
|
2538
|
+
check(flyoutOf('外观') === null, 'clicking the row again closes it')
|
|
2539
|
+
|
|
2540
|
+
/* 手风琴:悬停到另一行时,上一个面板让位 */
|
|
2541
|
+
groupRow('外观').dispatch('click', itemEvent())
|
|
2542
|
+
check(flyoutOf('外观') !== null, 'pin one open again')
|
|
2543
|
+
hoverIn('行为', 'mouse')
|
|
2544
|
+
h.advance(200, 40)
|
|
2545
|
+
check(flyoutOf('行为') !== null && flyoutOf('外观') === null,
|
|
2546
|
+
'hovering another row opens its submenu and closes the previous one',
|
|
2547
|
+
`外观=${flyoutOf('外观') !== null} 行为=${flyoutOf('行为') !== null}`)
|
|
2548
|
+
|
|
2549
|
+
/* 键盘:→ 进子菜单、← 回父行、Esc 逐层退出 */
|
|
2550
|
+
const focusedIs = (pred) => {
|
|
2551
|
+
const el = h.document.activeElement
|
|
2552
|
+
return Boolean(el && pred(el))
|
|
2553
|
+
}
|
|
2554
|
+
const focusRow = (name) => {
|
|
2555
|
+
pressKey('Home')
|
|
2556
|
+
for (let i = 0; i < 16; i++) {
|
|
2557
|
+
if (focusedIs((el) => el.dataset && el.dataset.group === name)) return true
|
|
2558
|
+
pressKey('ArrowDown')
|
|
2559
|
+
}
|
|
2560
|
+
return false
|
|
2561
|
+
}
|
|
2562
|
+
check(focusRow('位置'), 'keyboard focus can land on a group row')
|
|
2563
|
+
pressKey('ArrowRight')
|
|
2564
|
+
check(flyoutOf('位置') !== null, 'ArrowRight opens the submenu beside it')
|
|
2565
|
+
check(focusedIs((el) => el.className.indexOf('wisp-corner') >= 0),
|
|
2566
|
+
'and focus moves into it', String(h.document.activeElement && h.document.activeElement.className))
|
|
2567
|
+
pressKey('ArrowLeft')
|
|
2568
|
+
check(flyoutOf('位置') === null, 'ArrowLeft closes it')
|
|
2569
|
+
check(focusedIs((el) => el.dataset && el.dataset.group === '位置'),
|
|
2570
|
+
'and hands focus back to the row',
|
|
2571
|
+
String(h.document.activeElement && h.document.activeElement.dataset && h.document.activeElement.dataset.group))
|
|
2572
|
+
pressKey('ArrowRight')
|
|
2573
|
+
check(flyoutOf('位置') !== null, 'open it again for the Escape check')
|
|
2574
|
+
pressKey('Escape')
|
|
2575
|
+
check(flyoutOf('位置') === null && menuOf() !== null,
|
|
2576
|
+
'Escape closes the submenu first and leaves the menu open (one layer at a time)',
|
|
2577
|
+
`面板=${flyoutOf('位置') === null ? '关了' : '还开着'} 菜单=${menuOf() === null ? '被关了' : '还开着'}`)
|
|
2578
|
+
pressKey('Escape')
|
|
2579
|
+
check(menuOf() === null, 'and a second Escape closes the menu')
|
|
2580
|
+
body5.dispatch('contextmenu', rightClickOnHer().ev)
|
|
2581
|
+
check(menuOf() !== null, 'the menu reopens for the remaining structure checks')
|
|
2456
2582
|
|
|
2457
2583
|
/* ---- 菜单里不该再有"版本与更新":版本号在头部,检查更新在弹窗里 ---- */
|
|
2458
2584
|
check(!itemsOf().some((i) => i.textContent.includes('版本与更新')),
|
|
@@ -2512,7 +2638,8 @@ if (clientSrc !== null) {
|
|
|
2512
2638
|
body5.dispatch('contextmenu', secondClick.ev)
|
|
2513
2639
|
check(menuOf() !== null, 'the menu reopens')
|
|
2514
2640
|
check(expand('位置'), 'the corners live in the position group')
|
|
2515
|
-
const homeBtn =
|
|
2641
|
+
const homeBtn = menuHost().querySelectorAll('.wisp-corner')
|
|
2642
|
+
.find((b) => b.removed !== true && b.dataset && b.dataset.corner === '右下角')
|
|
2516
2643
|
check(Boolean(homeBtn), 'the corner button is there')
|
|
2517
2644
|
homeBtn.dispatch('click', itemEvent())
|
|
2518
2645
|
const homed = fifth.position
|