@selkit/dom 0.2.0 → 0.3.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/dist/index.cjs CHANGED
@@ -170,6 +170,8 @@ var SelkitDom = class {
170
170
  #hiddenContainer = null;
171
171
  #selectPrevDisplay = "";
172
172
  #dragFrom = -1;
173
+ /** 上次已捲入可視的 activeIndex 僅在變動時才捲 避免跟使用者手動捲動打架 */
174
+ #lastActive = -1;
173
175
  #positioner = null;
174
176
  #unsubscribe;
175
177
  #offClose;
@@ -442,6 +444,37 @@ var SelkitDom = class {
442
444
  this.#syncForm();
443
445
  this.element.classList.toggle(this.#cls("", "open"), s.isOpen);
444
446
  this.element.classList.toggle(this.#cls("", "disabled"), s.disabled);
447
+ this.#scrollActiveIntoView(s);
448
+ }
449
+ /**
450
+ * 鍵盤導航/開啟時讓作用中選項保持可見(aria-activedescendant 完整度)
451
+ * 僅在 activeIndex 變動時動作 手動捲動觸發的重繪不會跟著捲
452
+ */
453
+ #scrollActiveIntoView(s) {
454
+ if (!s.isOpen) {
455
+ this.#lastActive = -1;
456
+ return;
457
+ }
458
+ if (s.activeIndex === this.#lastActive || s.activeIndex < 0) return;
459
+ this.#lastActive = s.activeIndex;
460
+ const hasGroups = this.controller.getGroupedView().rows.some((r) => r.type === "group");
461
+ if (this.#virtual && !hasGroups) {
462
+ const next = (0, import_core.computeScrollIntoView)({
463
+ index: s.activeIndex,
464
+ scrollTop: this.#dropdown.scrollTop,
465
+ viewportHeight: this.#dropdown.clientHeight,
466
+ itemHeight: this.#itemHeight
467
+ });
468
+ if (next !== null) {
469
+ this.#dropdown.scrollTop = next;
470
+ this.#renderOptions(s);
471
+ }
472
+ return;
473
+ }
474
+ const active = this.#dropdown.querySelector(
475
+ `.${this.#cls("option", "active")}`
476
+ );
477
+ active?.scrollIntoView?.({ block: "nearest" });
445
478
  }
446
479
  /** 套用模板輸出:字串走 textContent(防 XSS)Node 直接掛入 */
447
480
  #applyTemplate(host, out) {
package/dist/index.js CHANGED
@@ -1,5 +1,9 @@
1
1
  // src/dom.ts
2
- import { computeVirtualRange, createSelkit } from "@selkit/core";
2
+ import {
3
+ computeScrollIntoView,
4
+ computeVirtualRange,
5
+ createSelkit
6
+ } from "@selkit/core";
3
7
 
4
8
  // src/positioner.ts
5
9
  function computePosition(triggerRect, dropdownHeight, viewportHeight, gap = 4) {
@@ -139,6 +143,8 @@ var SelkitDom = class {
139
143
  #hiddenContainer = null;
140
144
  #selectPrevDisplay = "";
141
145
  #dragFrom = -1;
146
+ /** 上次已捲入可視的 activeIndex 僅在變動時才捲 避免跟使用者手動捲動打架 */
147
+ #lastActive = -1;
142
148
  #positioner = null;
143
149
  #unsubscribe;
144
150
  #offClose;
@@ -411,6 +417,37 @@ var SelkitDom = class {
411
417
  this.#syncForm();
412
418
  this.element.classList.toggle(this.#cls("", "open"), s.isOpen);
413
419
  this.element.classList.toggle(this.#cls("", "disabled"), s.disabled);
420
+ this.#scrollActiveIntoView(s);
421
+ }
422
+ /**
423
+ * 鍵盤導航/開啟時讓作用中選項保持可見(aria-activedescendant 完整度)
424
+ * 僅在 activeIndex 變動時動作 手動捲動觸發的重繪不會跟著捲
425
+ */
426
+ #scrollActiveIntoView(s) {
427
+ if (!s.isOpen) {
428
+ this.#lastActive = -1;
429
+ return;
430
+ }
431
+ if (s.activeIndex === this.#lastActive || s.activeIndex < 0) return;
432
+ this.#lastActive = s.activeIndex;
433
+ const hasGroups = this.controller.getGroupedView().rows.some((r) => r.type === "group");
434
+ if (this.#virtual && !hasGroups) {
435
+ const next = computeScrollIntoView({
436
+ index: s.activeIndex,
437
+ scrollTop: this.#dropdown.scrollTop,
438
+ viewportHeight: this.#dropdown.clientHeight,
439
+ itemHeight: this.#itemHeight
440
+ });
441
+ if (next !== null) {
442
+ this.#dropdown.scrollTop = next;
443
+ this.#renderOptions(s);
444
+ }
445
+ return;
446
+ }
447
+ const active = this.#dropdown.querySelector(
448
+ `.${this.#cls("option", "active")}`
449
+ );
450
+ active?.scrollIntoView?.({ block: "nearest" });
414
451
  }
415
452
  /** 套用模板輸出:字串走 textContent(防 XSS)Node 直接掛入 */
416
453
  #applyTemplate(host, out) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@selkit/dom",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Vanilla JS renderer for Selkit — DOM, events, a11y and default positioner.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -19,7 +19,7 @@
19
19
  ],
20
20
  "sideEffects": false,
21
21
  "dependencies": {
22
- "@selkit/core": "0.2.0"
22
+ "@selkit/core": "0.3.0"
23
23
  },
24
24
  "devDependencies": {
25
25
  "axe-core": "^4.12.1",