@selkit/dom 0.3.0 → 0.4.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 +73 -32
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +75 -32
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -72,6 +72,7 @@ function attachPositioner(trigger, dropdown, autoWidth = false) {
|
|
|
72
72
|
// src/dom.ts
|
|
73
73
|
var LOAD_MORE_THRESHOLD = 32;
|
|
74
74
|
var DEFAULT_ITEM_HEIGHT = 36;
|
|
75
|
+
var DEFAULT_GROUP_HEIGHT = 28;
|
|
75
76
|
var SR_ONLY_CSS = "position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0";
|
|
76
77
|
function resolveParent(parent) {
|
|
77
78
|
if (!parent) return null;
|
|
@@ -152,6 +153,7 @@ var SelkitDom = class {
|
|
|
152
153
|
#name;
|
|
153
154
|
#virtual;
|
|
154
155
|
#itemHeight;
|
|
156
|
+
#groupHeight;
|
|
155
157
|
#dropdownParent;
|
|
156
158
|
#templateSelection;
|
|
157
159
|
#templateOption;
|
|
@@ -196,6 +198,7 @@ var SelkitDom = class {
|
|
|
196
198
|
this.#name = cfg.name;
|
|
197
199
|
this.#virtual = cfg.virtualScroll ?? false;
|
|
198
200
|
this.#itemHeight = cfg.itemHeight ?? DEFAULT_ITEM_HEIGHT;
|
|
201
|
+
this.#groupHeight = cfg.groupHeight ?? DEFAULT_GROUP_HEIGHT;
|
|
199
202
|
this.#dropdownParent = resolveParent(cfg.dropdownParent);
|
|
200
203
|
this.#templateSelection = cfg.templateSelection;
|
|
201
204
|
this.#templateOption = cfg.templateOption;
|
|
@@ -457,7 +460,8 @@ var SelkitDom = class {
|
|
|
457
460
|
}
|
|
458
461
|
if (s.activeIndex === this.#lastActive || s.activeIndex < 0) return;
|
|
459
462
|
this.#lastActive = s.activeIndex;
|
|
460
|
-
const
|
|
463
|
+
const rows = this.controller.getGroupedView().rows;
|
|
464
|
+
const hasGroups = rows.some((r) => r.type === "group");
|
|
461
465
|
if (this.#virtual && !hasGroups) {
|
|
462
466
|
const next = (0, import_core.computeScrollIntoView)({
|
|
463
467
|
index: s.activeIndex,
|
|
@@ -471,6 +475,22 @@ var SelkitDom = class {
|
|
|
471
475
|
}
|
|
472
476
|
return;
|
|
473
477
|
}
|
|
478
|
+
if (this.#virtual && hasGroups) {
|
|
479
|
+
const rowIndex = rows.findIndex(
|
|
480
|
+
(r) => r.type !== "group" && r.index === s.activeIndex
|
|
481
|
+
);
|
|
482
|
+
const next = (0, import_core.computeScrollIntoViewVariable)({
|
|
483
|
+
heights: this.#rowHeights(rows),
|
|
484
|
+
rowIndex,
|
|
485
|
+
scrollTop: this.#dropdown.scrollTop,
|
|
486
|
+
viewportHeight: this.#dropdown.clientHeight
|
|
487
|
+
});
|
|
488
|
+
if (next !== null) {
|
|
489
|
+
this.#dropdown.scrollTop = next;
|
|
490
|
+
this.#renderOptions(s);
|
|
491
|
+
}
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
474
494
|
const active = this.#dropdown.querySelector(
|
|
475
495
|
`.${this.#cls("option", "active")}`
|
|
476
496
|
);
|
|
@@ -588,40 +608,61 @@ var SelkitDom = class {
|
|
|
588
608
|
itemHeight: this.#itemHeight,
|
|
589
609
|
itemCount: view.rows.length
|
|
590
610
|
});
|
|
591
|
-
this.#
|
|
592
|
-
for (let i = range.startIndex; i < range.endIndex; i++) {
|
|
593
|
-
const row = view.rows[i];
|
|
594
|
-
if (row?.type === "option") {
|
|
595
|
-
this.#dropdown.append(this.#buildOption(row, a11y, s.activeIndex));
|
|
596
|
-
} else if (row?.type === "create") {
|
|
597
|
-
this.#dropdown.append(this.#buildCreateRow(row, a11y, s.activeIndex));
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
this.#dropdown.append(this.#spacer(range.paddingBottom));
|
|
611
|
+
this.#renderVirtualSlice(view.rows, range, a11y, s.activeIndex);
|
|
601
612
|
return;
|
|
602
613
|
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
614
|
+
if (this.#virtual && hasGroups) {
|
|
615
|
+
const win = (0, import_core.computeVirtualWindow)({
|
|
616
|
+
heights: this.#rowHeights(view.rows),
|
|
617
|
+
scrollTop: this.#dropdown.scrollTop,
|
|
618
|
+
viewportHeight: this.#dropdown.clientHeight
|
|
619
|
+
});
|
|
620
|
+
this.#renderVirtualSlice(view.rows, win, a11y, s.activeIndex);
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
for (const row of view.rows) this.#renderRow(row, a11y, s.activeIndex);
|
|
624
|
+
}
|
|
625
|
+
/** 每列高度:分組標題用 groupHeight 其餘(option/create)用 itemHeight */
|
|
626
|
+
#rowHeights(rows) {
|
|
627
|
+
return rows.map(
|
|
628
|
+
(r) => r.type === "group" ? this.#groupHeight : this.#itemHeight
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
/** 渲染虛擬切片:上下佔位 + range 內各列 共用扁平與分組兩路徑 */
|
|
632
|
+
#renderVirtualSlice(rows, range, a11y, activeIndex) {
|
|
633
|
+
this.#dropdown.append(this.#spacer(range.paddingTop));
|
|
634
|
+
for (let i = range.startIndex; i < range.endIndex; i++) {
|
|
635
|
+
const row = rows[i];
|
|
636
|
+
if (row) this.#renderRow(row, a11y, activeIndex);
|
|
637
|
+
}
|
|
638
|
+
this.#dropdown.append(this.#spacer(range.paddingBottom));
|
|
639
|
+
}
|
|
640
|
+
/** 依列型別渲染並掛入下拉 group / create / option */
|
|
641
|
+
#renderRow(row, a11y, activeIndex) {
|
|
642
|
+
if (row.type === "group") {
|
|
643
|
+
this.#dropdown.append(this.#buildGroupRow(row));
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
if (row.type === "create") {
|
|
647
|
+
this.#dropdown.append(this.#buildCreateRow(row, a11y, activeIndex));
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
this.#dropdown.append(this.#buildOption(row, a11y, activeIndex));
|
|
651
|
+
}
|
|
652
|
+
/** 分組標題列 套用 templateGroup(無則用 label)*/
|
|
653
|
+
#buildGroupRow(row) {
|
|
654
|
+
const group = document.createElement("div");
|
|
655
|
+
group.className = this.#cls("group");
|
|
656
|
+
if (row.disabled) group.classList.add(this.#cls("group", "disabled"));
|
|
657
|
+
if (this.#templateGroup) {
|
|
658
|
+
this.#applyTemplate(
|
|
659
|
+
group,
|
|
660
|
+
this.#templateGroup({ label: row.label, disabled: !!row.disabled })
|
|
661
|
+
);
|
|
662
|
+
} else {
|
|
663
|
+
group.textContent = row.label;
|
|
624
664
|
}
|
|
665
|
+
return group;
|
|
625
666
|
}
|
|
626
667
|
/** 「建立新項」列 共用 option 樣式與 a11y 但點擊走 createTag */
|
|
627
668
|
#buildCreateRow(row, a11y, activeIndex) {
|
package/dist/index.d.cts
CHANGED
|
@@ -15,6 +15,8 @@ interface SelkitDomConfig<T = unknown> extends SelkitConfig<T> {
|
|
|
15
15
|
virtualScroll?: boolean;
|
|
16
16
|
/** 虛擬捲動的單列固定高度 px 預設 36 須與實際樣式高度一致 */
|
|
17
17
|
itemHeight?: number;
|
|
18
|
+
/** 虛擬捲動下分組標題列的固定高度 px 預設 28 須與實際樣式高度一致 */
|
|
19
|
+
groupHeight?: number;
|
|
18
20
|
/** 把下拉浮層掛到指定容器(元素或選擇器)逃離 overflow/transform 祖先的裁切 常用 document.body */
|
|
19
21
|
dropdownParent?: HTMLElement | string;
|
|
20
22
|
/** 自訂已選顯示內容(tag 或單值) 回傳字串走 textContent 防 XSS 需 markup(icon 等)請回傳 Node */
|
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ interface SelkitDomConfig<T = unknown> extends SelkitConfig<T> {
|
|
|
15
15
|
virtualScroll?: boolean;
|
|
16
16
|
/** 虛擬捲動的單列固定高度 px 預設 36 須與實際樣式高度一致 */
|
|
17
17
|
itemHeight?: number;
|
|
18
|
+
/** 虛擬捲動下分組標題列的固定高度 px 預設 28 須與實際樣式高度一致 */
|
|
19
|
+
groupHeight?: number;
|
|
18
20
|
/** 把下拉浮層掛到指定容器(元素或選擇器)逃離 overflow/transform 祖先的裁切 常用 document.body */
|
|
19
21
|
dropdownParent?: HTMLElement | string;
|
|
20
22
|
/** 自訂已選顯示內容(tag 或單值) 回傳字串走 textContent 防 XSS 需 markup(icon 等)請回傳 Node */
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// src/dom.ts
|
|
2
2
|
import {
|
|
3
3
|
computeScrollIntoView,
|
|
4
|
+
computeScrollIntoViewVariable,
|
|
4
5
|
computeVirtualRange,
|
|
6
|
+
computeVirtualWindow,
|
|
5
7
|
createSelkit
|
|
6
8
|
} from "@selkit/core";
|
|
7
9
|
|
|
@@ -45,6 +47,7 @@ function attachPositioner(trigger, dropdown, autoWidth = false) {
|
|
|
45
47
|
// src/dom.ts
|
|
46
48
|
var LOAD_MORE_THRESHOLD = 32;
|
|
47
49
|
var DEFAULT_ITEM_HEIGHT = 36;
|
|
50
|
+
var DEFAULT_GROUP_HEIGHT = 28;
|
|
48
51
|
var SR_ONLY_CSS = "position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0";
|
|
49
52
|
function resolveParent(parent) {
|
|
50
53
|
if (!parent) return null;
|
|
@@ -125,6 +128,7 @@ var SelkitDom = class {
|
|
|
125
128
|
#name;
|
|
126
129
|
#virtual;
|
|
127
130
|
#itemHeight;
|
|
131
|
+
#groupHeight;
|
|
128
132
|
#dropdownParent;
|
|
129
133
|
#templateSelection;
|
|
130
134
|
#templateOption;
|
|
@@ -169,6 +173,7 @@ var SelkitDom = class {
|
|
|
169
173
|
this.#name = cfg.name;
|
|
170
174
|
this.#virtual = cfg.virtualScroll ?? false;
|
|
171
175
|
this.#itemHeight = cfg.itemHeight ?? DEFAULT_ITEM_HEIGHT;
|
|
176
|
+
this.#groupHeight = cfg.groupHeight ?? DEFAULT_GROUP_HEIGHT;
|
|
172
177
|
this.#dropdownParent = resolveParent(cfg.dropdownParent);
|
|
173
178
|
this.#templateSelection = cfg.templateSelection;
|
|
174
179
|
this.#templateOption = cfg.templateOption;
|
|
@@ -430,7 +435,8 @@ var SelkitDom = class {
|
|
|
430
435
|
}
|
|
431
436
|
if (s.activeIndex === this.#lastActive || s.activeIndex < 0) return;
|
|
432
437
|
this.#lastActive = s.activeIndex;
|
|
433
|
-
const
|
|
438
|
+
const rows = this.controller.getGroupedView().rows;
|
|
439
|
+
const hasGroups = rows.some((r) => r.type === "group");
|
|
434
440
|
if (this.#virtual && !hasGroups) {
|
|
435
441
|
const next = computeScrollIntoView({
|
|
436
442
|
index: s.activeIndex,
|
|
@@ -444,6 +450,22 @@ var SelkitDom = class {
|
|
|
444
450
|
}
|
|
445
451
|
return;
|
|
446
452
|
}
|
|
453
|
+
if (this.#virtual && hasGroups) {
|
|
454
|
+
const rowIndex = rows.findIndex(
|
|
455
|
+
(r) => r.type !== "group" && r.index === s.activeIndex
|
|
456
|
+
);
|
|
457
|
+
const next = computeScrollIntoViewVariable({
|
|
458
|
+
heights: this.#rowHeights(rows),
|
|
459
|
+
rowIndex,
|
|
460
|
+
scrollTop: this.#dropdown.scrollTop,
|
|
461
|
+
viewportHeight: this.#dropdown.clientHeight
|
|
462
|
+
});
|
|
463
|
+
if (next !== null) {
|
|
464
|
+
this.#dropdown.scrollTop = next;
|
|
465
|
+
this.#renderOptions(s);
|
|
466
|
+
}
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
447
469
|
const active = this.#dropdown.querySelector(
|
|
448
470
|
`.${this.#cls("option", "active")}`
|
|
449
471
|
);
|
|
@@ -561,40 +583,61 @@ var SelkitDom = class {
|
|
|
561
583
|
itemHeight: this.#itemHeight,
|
|
562
584
|
itemCount: view.rows.length
|
|
563
585
|
});
|
|
564
|
-
this.#
|
|
565
|
-
for (let i = range.startIndex; i < range.endIndex; i++) {
|
|
566
|
-
const row = view.rows[i];
|
|
567
|
-
if (row?.type === "option") {
|
|
568
|
-
this.#dropdown.append(this.#buildOption(row, a11y, s.activeIndex));
|
|
569
|
-
} else if (row?.type === "create") {
|
|
570
|
-
this.#dropdown.append(this.#buildCreateRow(row, a11y, s.activeIndex));
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
this.#dropdown.append(this.#spacer(range.paddingBottom));
|
|
586
|
+
this.#renderVirtualSlice(view.rows, range, a11y, s.activeIndex);
|
|
574
587
|
return;
|
|
575
588
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
589
|
+
if (this.#virtual && hasGroups) {
|
|
590
|
+
const win = computeVirtualWindow({
|
|
591
|
+
heights: this.#rowHeights(view.rows),
|
|
592
|
+
scrollTop: this.#dropdown.scrollTop,
|
|
593
|
+
viewportHeight: this.#dropdown.clientHeight
|
|
594
|
+
});
|
|
595
|
+
this.#renderVirtualSlice(view.rows, win, a11y, s.activeIndex);
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
for (const row of view.rows) this.#renderRow(row, a11y, s.activeIndex);
|
|
599
|
+
}
|
|
600
|
+
/** 每列高度:分組標題用 groupHeight 其餘(option/create)用 itemHeight */
|
|
601
|
+
#rowHeights(rows) {
|
|
602
|
+
return rows.map(
|
|
603
|
+
(r) => r.type === "group" ? this.#groupHeight : this.#itemHeight
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
/** 渲染虛擬切片:上下佔位 + range 內各列 共用扁平與分組兩路徑 */
|
|
607
|
+
#renderVirtualSlice(rows, range, a11y, activeIndex) {
|
|
608
|
+
this.#dropdown.append(this.#spacer(range.paddingTop));
|
|
609
|
+
for (let i = range.startIndex; i < range.endIndex; i++) {
|
|
610
|
+
const row = rows[i];
|
|
611
|
+
if (row) this.#renderRow(row, a11y, activeIndex);
|
|
612
|
+
}
|
|
613
|
+
this.#dropdown.append(this.#spacer(range.paddingBottom));
|
|
614
|
+
}
|
|
615
|
+
/** 依列型別渲染並掛入下拉 group / create / option */
|
|
616
|
+
#renderRow(row, a11y, activeIndex) {
|
|
617
|
+
if (row.type === "group") {
|
|
618
|
+
this.#dropdown.append(this.#buildGroupRow(row));
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
if (row.type === "create") {
|
|
622
|
+
this.#dropdown.append(this.#buildCreateRow(row, a11y, activeIndex));
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
this.#dropdown.append(this.#buildOption(row, a11y, activeIndex));
|
|
626
|
+
}
|
|
627
|
+
/** 分組標題列 套用 templateGroup(無則用 label)*/
|
|
628
|
+
#buildGroupRow(row) {
|
|
629
|
+
const group = document.createElement("div");
|
|
630
|
+
group.className = this.#cls("group");
|
|
631
|
+
if (row.disabled) group.classList.add(this.#cls("group", "disabled"));
|
|
632
|
+
if (this.#templateGroup) {
|
|
633
|
+
this.#applyTemplate(
|
|
634
|
+
group,
|
|
635
|
+
this.#templateGroup({ label: row.label, disabled: !!row.disabled })
|
|
636
|
+
);
|
|
637
|
+
} else {
|
|
638
|
+
group.textContent = row.label;
|
|
597
639
|
}
|
|
640
|
+
return group;
|
|
598
641
|
}
|
|
599
642
|
/** 「建立新項」列 共用 option 樣式與 a11y 但點擊走 createTag */
|
|
600
643
|
#buildCreateRow(row, a11y, activeIndex) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@selkit/dom",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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.
|
|
22
|
+
"@selkit/core": "0.4.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"axe-core": "^4.12.1",
|