@hardanonymous/marquee-selector 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 hardanonymous
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,198 @@
1
+ # Marquee Selector
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@hardanonymous/marquee-selector.svg)](https://www.npmjs.com/package/@hardanonymous/marquee-selector)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ 一個框架無關的 JavaScript/TypeScript 框選工具,允許使用者通過滑鼠拖曳選取頁面上的元素。
7
+
8
+ ## ✨ 特性
9
+
10
+ - 🎯 **框架無關**:純 Vanilla JavaScript 實現,可在任何前端框架中使用
11
+ - 🎨 **多目標支援**:可同時追蹤多組不同的選取目標,各有獨立的回調
12
+ - 🛡️ **智能保護機制**:
13
+ - 自動保護可拖拽元素(`draggable="true"`)
14
+ - 保護文字選取區域(input、textarea、contenteditable 等)
15
+ - 🔄 **完整生命週期**:提供 `onSelectionStart`、`onSelectionChange`、`onSelectionEnd`、`onClearClick` 回調
16
+ - 📍 **精確碰撞檢測**:支援滾動容器,使用 AABB 算法進行精確的碰撞檢測
17
+ - 💾 **記憶體優化**:使用 WeakMap 快取選取狀態,避免記憶體洩漏
18
+ - 🎨 **CSS 可定制**:通過 CSS 變數輕鬆定制外觀
19
+
20
+ ## 📦 安裝
21
+
22
+ ```bash
23
+ npm install @hardanonymous/marquee-selector
24
+ ```
25
+
26
+ 或使用 yarn:
27
+
28
+ ```bash
29
+ yarn add @hardanonymous/marquee-selector
30
+ ```
31
+
32
+ ## 🚀 快速開始
33
+
34
+ ```typescript
35
+ import { MarqueeSelector } from '@hardanonymous/marquee-selector';
36
+ import '@hardanonymous/marquee-selector/style.css';
37
+
38
+ // 1. 初始化
39
+ const marquee = new MarqueeSelector({
40
+ container: document.body
41
+ });
42
+
43
+ // 2. 添加目標配置
44
+ marquee.addTarget({
45
+ selector: '.item',
46
+ onSelectionChange: (elements) => {
47
+ console.log('選取變化:', elements);
48
+ elements.forEach(el => el.classList.add('selected'));
49
+ },
50
+ onClearClick: (elements) => {
51
+ console.log('清除選取');
52
+ elements.forEach(el => el.classList.remove('selected'));
53
+ }
54
+ });
55
+
56
+ // 3. 啟用框選
57
+ marquee.enable();
58
+ ```
59
+
60
+ ## 📚 使用範例
61
+
62
+ ### Vue 3
63
+
64
+ ```vue
65
+ <script setup lang="ts">
66
+ import { ref, onMounted, onUnmounted } from 'vue';
67
+ import { MarqueeSelector } from '@hardanonymous/marquee-selector';
68
+ import '@hardanonymous/marquee-selector/style.css';
69
+
70
+ const containerRef = ref<HTMLElement>();
71
+ const selectedIds = ref<number[]>([]);
72
+ let marquee: MarqueeSelector | null = null;
73
+
74
+ onMounted(() => {
75
+ if (containerRef.value) {
76
+ marquee = new MarqueeSelector({
77
+ container: containerRef.value
78
+ });
79
+
80
+ marquee.addTarget({
81
+ selector: '.item',
82
+ onSelectionChange: (elements) => {
83
+ selectedIds.value = elements.map(el =>
84
+ Number(el.getAttribute('data-id'))
85
+ );
86
+ }
87
+ });
88
+
89
+ marquee.enable();
90
+ }
91
+ });
92
+
93
+ onUnmounted(() => {
94
+ marquee?.destroy();
95
+ });
96
+ </script>
97
+
98
+ <template>
99
+ <div ref="containerRef">
100
+ <div
101
+ v-for="item in items"
102
+ :key="item.id"
103
+ :data-id="item.id"
104
+ class="item"
105
+ :class="{ selected: selectedIds.includes(item.id) }"
106
+ >
107
+ {{ item.name }}
108
+ </div>
109
+ </div>
110
+ </template>
111
+ ```
112
+
113
+ ### React
114
+
115
+ ```tsx
116
+ import { useEffect, useRef, useState } from 'react';
117
+ import { MarqueeSelector } from '@hardanonymous/marquee-selector';
118
+ import '@hardanonymous/marquee-selector/style.css';
119
+
120
+ function App() {
121
+ const containerRef = useRef<HTMLDivElement>(null);
122
+ const [selectedIds, setSelectedIds] = useState<number[]>([]);
123
+
124
+ useEffect(() => {
125
+ if (!containerRef.current) return;
126
+
127
+ const marquee = new MarqueeSelector({
128
+ container: containerRef.current
129
+ });
130
+
131
+ marquee.addTarget({
132
+ selector: '.item',
133
+ onSelectionChange: (elements) => {
134
+ const ids = elements.map(el =>
135
+ Number(el.getAttribute('data-id'))
136
+ );
137
+ setSelectedIds(ids);
138
+ }
139
+ });
140
+
141
+ marquee.enable();
142
+
143
+ return () => marquee.destroy();
144
+ }, []);
145
+
146
+ return (
147
+ <div ref={containerRef}>
148
+ {items.map(item => (
149
+ <div
150
+ key={item.id}
151
+ data-id={item.id}
152
+ className={`item ${selectedIds.includes(item.id) ? 'selected' : ''}`}
153
+ >
154
+ {item.name}
155
+ </div>
156
+ ))}
157
+ </div>
158
+ );
159
+ }
160
+ ```
161
+
162
+ ## 📖 完整文檔
163
+
164
+ 查看 [完整文檔](./docs/README.md) 了解:
165
+ - 詳細的 API 參考
166
+ - 進階使用方式
167
+ - 與拖拽功能整合
168
+ - 工作原理說明
169
+ - 效能優化建議
170
+ - 常見問題解答
171
+
172
+ ## 🎨 自定義樣式
173
+
174
+ 通過 CSS 變數自定義框選框外觀:
175
+
176
+ ```css
177
+ :root {
178
+ --marquee-border-width: 2px;
179
+ --marquee-border-style: dashed;
180
+ --marquee-border-color: #ff0000;
181
+ --marquee-bg-color: rgba(255, 0, 0, 0.15);
182
+ --marquee-z-index: 10000;
183
+ }
184
+ ```
185
+
186
+ ## 🤝 貢獻
187
+
188
+ 歡迎提交 Issue 和 Pull Request!
189
+
190
+ ## 📄 授權
191
+
192
+ MIT License - 詳見 [LICENSE](./LICENSE) 文件
193
+
194
+ ## 🔗 相關連結
195
+
196
+ - [GitHub Repository](https://github.com/hardanonymous/marquee-selector)
197
+ - [npm Package](https://www.npmjs.com/package/@hardanonymous/marquee-selector)
198
+ - [問題回報](https://github.com/hardanonymous/marquee-selector/issues)
@@ -0,0 +1,84 @@
1
+ export interface MarqueeSelectorOptions {
2
+ /**
3
+ * 容器元素的實例或選擇器字串
4
+ * 這是滑鼠事件監聽的目標,也是計算相對座標的基準。
5
+ */
6
+ container: HTMLElement | string;
7
+ /**
8
+ * 允許文字選取的元素選擇器(這些元素上不會啟動框選,可以正常反白複製文字)
9
+ * 例如: ['p', '.text-content', '[contenteditable]']
10
+ * 預設會自動包含: input, textarea, [contenteditable]
11
+ */
12
+ allowTextSelectionOn?: string[];
13
+ }
14
+ export interface MarqueeTargetConfig {
15
+ /**
16
+ * 可以是單一或多個 CSS 選擇器,或直接傳入 HTMLElement(或其陣列)。
17
+ */
18
+ selector: string | string[] | HTMLElement | HTMLElement[];
19
+ /**
20
+ * 專屬於此目標的選取變化回調。
21
+ */
22
+ onSelectionChange?: (selectedElements: HTMLElement[]) => void;
23
+ /**
24
+ * 框選開始時的回調函式
25
+ * 在滑鼠按下並開始框選時觸發,會傳入點擊的目標元素
26
+ */
27
+ onSelectionStart?: (targetElement: HTMLElement) => void;
28
+ /**
29
+ * 框選結束時的回調函式
30
+ * 在滑鼠放開、框選框消失時觸發,會傳入該 target 當前被選取的元素陣列
31
+ */
32
+ onSelectionEnd?: (selectedElements: HTMLElement[]) => void;
33
+ /**
34
+ * 當框選結束後,使用者點擊空白處(非該 target 元素)時的回調函式
35
+ * 會傳入當前被選取的元素陣列
36
+ * 使用者可以在此回調中決定是否清除選取、執行其他操作等
37
+ */
38
+ onClearClick?: (selectedElements: HTMLElement[]) => void;
39
+ }
40
+ export default class MarqueeSelector {
41
+ private container;
42
+ private enabled;
43
+ private targets;
44
+ private targetSelectionCache;
45
+ private allowTextSelectionSelectors;
46
+ private isSelecting;
47
+ private hasStarted;
48
+ private isClearClickListenerActive;
49
+ private selectionStart;
50
+ private selectionCurrent;
51
+ private selectionBox;
52
+ private _onMouseMove;
53
+ private _onMouseUp;
54
+ private _onMouseDown;
55
+ private _onClearClick;
56
+ constructor(options: MarqueeSelectorOptions);
57
+ private initSelectionBox;
58
+ private onMouseDown;
59
+ private onMouseMove;
60
+ private onMouseUp;
61
+ private onClearClick;
62
+ private updateBoxStyle;
63
+ /**
64
+ * 更新選取狀態,執行碰撞檢測
65
+ *
66
+ * 此方法會:
67
+ * 1. 將框選框的相對座標轉換為 Client 座標
68
+ * 2. 遍歷所有 target 的候選元素
69
+ * 3. 使用 AABB 碰撞檢測判斷元素是否與框選框相交
70
+ * 4. 比較新舊選取狀態,若有變化則更新快取並觸發 onSelectionChange 回調
71
+ */
72
+ private updateSelection;
73
+ enable(): void;
74
+ disable(): void;
75
+ isEnabled(): boolean;
76
+ addTarget(config: MarqueeTargetConfig): void;
77
+ destroy(): void;
78
+ private resolveCandidates;
79
+ /**
80
+ * 檢查是否應該允許在目標元素上進行文字選取
81
+ */
82
+ private isTextSelectionAllowed;
83
+ }
84
+ //# sourceMappingURL=MarqueeSelector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MarqueeSelector.d.ts","sourceRoot":"","sources":["../src/MarqueeSelector.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,SAAS,EAAE,WAAW,GAAG,MAAM,CAAC;IAEhC;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,WAAW,GAAG,WAAW,EAAE,CAAC;IAE1D;;OAEG;IACH,iBAAiB,CAAC,EAAE,CAAC,gBAAgB,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC;IAE9D;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CAAC,aAAa,EAAE,WAAW,KAAK,IAAI,CAAC;IAExD;;;OAGG;IACH,cAAc,CAAC,EAAE,CAAC,gBAAgB,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC;IAE3D;;;;OAIG;IACH,YAAY,CAAC,EAAE,CAAC,gBAAgB,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC;CAC1D;AAED,MAAM,CAAC,OAAO,OAAO,eAAe;IAClC,OAAO,CAAC,SAAS,CAAc;IAC/B,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,oBAAoB,CAGxB;IACJ,OAAO,CAAC,2BAA2B,CAAW;IAE9C,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,0BAA0B,CAAkB;IACpD,OAAO,CAAC,cAAc,CAA4C;IAClE,OAAO,CAAC,gBAAgB,CAA4C;IAEpE,OAAO,CAAC,YAAY,CAAc;IAClC,OAAO,CAAC,YAAY,CAA+B;IACnD,OAAO,CAAC,UAAU,CAA6B;IAC/C,OAAO,CAAC,YAAY,CAA+B;IACnD,OAAO,CAAC,aAAa,CAAgC;gBAEzC,OAAO,EAAE,sBAAsB;IAuC3C,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,WAAW;IAoCnB,OAAO,CAAC,WAAW;IAyBnB,OAAO,CAAC,SAAS;IAwCjB,OAAO,CAAC,YAAY;IA4BpB,OAAO,CAAC,cAAc;IAiBtB;;;;;;;;OAQG;IACH,OAAO,CAAC,eAAe;IAsDhB,MAAM;IAIN,OAAO;IAIP,SAAS;IAKT,SAAS,CAAC,MAAM,EAAE,mBAAmB;IAWrC,OAAO;IAWd,OAAO,CAAC,iBAAiB;IA2BzB;;OAEG;IACH,OAAO,CAAC,sBAAsB;CAqB/B"}
package/dist/index.cjs ADDED
@@ -0,0 +1,211 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+
7
+ class MarqueeSelector {
8
+ constructor(options) {
9
+ this.targets = [];
10
+ this.targetSelectionCache = new WeakMap;
11
+ this.isSelecting = false;
12
+ this.hasStarted = false;
13
+ this.isClearClickListenerActive = false;
14
+ this.selectionStart = {
15
+ x: 0,
16
+ y: 0
17
+ };
18
+ this.selectionCurrent = {
19
+ x: 0,
20
+ y: 0
21
+ };
22
+ this._onMouseMove = this.onMouseMove.bind(this);
23
+ this._onMouseUp = this.onMouseUp.bind(this);
24
+ this._onMouseDown = this.onMouseDown.bind(this);
25
+ this._onClearClick = this.onClearClick.bind(this);
26
+ if (typeof options.container === "string") {
27
+ const el = document.querySelector(options.container);
28
+ if (!el) throw new Error(`Container not found: ${options.container}`);
29
+ this.container = el;
30
+ } else this.container = options.container;
31
+ this.enabled = false;
32
+ const defaultTextSelectors = [ "input", "textarea", "[contenteditable]", "pre", "code" ];
33
+ this.allowTextSelectionSelectors = [ ...defaultTextSelectors, ...options.allowTextSelectionOn || [] ];
34
+ this.selectionBox = document.createElement("div");
35
+ this.initSelectionBox();
36
+ this.container.addEventListener("mousedown", this._onMouseDown);
37
+ const style = window.getComputedStyle(this.container);
38
+ if (style.position === "static") this.container.style.position = "relative";
39
+ }
40
+ initSelectionBox() {
41
+ this.selectionBox.className = "marquee-selection-box";
42
+ this.container.appendChild(this.selectionBox);
43
+ }
44
+ onMouseDown(event) {
45
+ if (!this.enabled) return;
46
+ if (event.button !== 0) return;
47
+ const target = event.target;
48
+ if (this.isTextSelectionAllowed(target) || target.draggable || target.closest('[draggable="true"]')) return;
49
+ const rect = this.container.getBoundingClientRect();
50
+ const startX = event.clientX - rect.left + this.container.scrollLeft;
51
+ const startY = event.clientY - rect.top + this.container.scrollTop;
52
+ this.selectionStart = {
53
+ x: startX,
54
+ y: startY
55
+ };
56
+ this.selectionCurrent = {
57
+ x: startX,
58
+ y: startY
59
+ };
60
+ this.isSelecting = true;
61
+ this.hasStarted = false;
62
+ this.updateBoxStyle();
63
+ this.selectionBox.classList.add("active");
64
+ document.body.classList.add("disable-user-select");
65
+ window.addEventListener("mousemove", this._onMouseMove);
66
+ window.addEventListener("mouseup", this._onMouseUp);
67
+ }
68
+ onMouseMove(event) {
69
+ if (!this.isSelecting) return;
70
+ if (!this.hasStarted) {
71
+ this.hasStarted = true;
72
+ const target = event.target;
73
+ for (const cfg of this.targets) if (cfg.onSelectionStart) cfg.onSelectionStart(target);
74
+ }
75
+ const rect = this.container.getBoundingClientRect();
76
+ const currentX = event.clientX - rect.left + this.container.scrollLeft;
77
+ const currentY = event.clientY - rect.top + this.container.scrollTop;
78
+ this.selectionCurrent = {
79
+ x: currentX,
80
+ y: currentY
81
+ };
82
+ this.updateBoxStyle();
83
+ this.updateSelection();
84
+ }
85
+ onMouseUp() {
86
+ if (this.isSelecting) {
87
+ this.isSelecting = false;
88
+ this.selectionBox.classList.remove("active");
89
+ if (this.hasStarted) {
90
+ for (const cfg of this.targets) if (cfg.onSelectionEnd) {
91
+ const selectedElements = this.targetSelectionCache.get(cfg) ?? [];
92
+ cfg.onSelectionEnd(selectedElements);
93
+ }
94
+ const hasAnyClearClickHandler = this.targets.some(cfg => {
95
+ if (!cfg.onClearClick) return false;
96
+ const selection = this.targetSelectionCache.get(cfg) ?? [];
97
+ return selection.length > 0;
98
+ });
99
+ if (hasAnyClearClickHandler && !this.isClearClickListenerActive) setTimeout(() => {
100
+ this.container.addEventListener("click", this._onClearClick);
101
+ this.isClearClickListenerActive = true;
102
+ }, 0);
103
+ }
104
+ }
105
+ window.removeEventListener("mousemove", this._onMouseMove);
106
+ window.removeEventListener("mouseup", this._onMouseUp);
107
+ document.body.classList.remove("disable-user-select");
108
+ }
109
+ onClearClick(event) {
110
+ const target = event.target;
111
+ for (const cfg of this.targets) {
112
+ if (!cfg.onClearClick) continue;
113
+ const currentSelection = this.targetSelectionCache.get(cfg) ?? [];
114
+ if (currentSelection.length === 0) continue;
115
+ const isClickOnTarget = currentSelection.some(el => el === target || el.contains(target));
116
+ if (!isClickOnTarget) {
117
+ cfg.onClearClick(currentSelection);
118
+ this.targetSelectionCache.set(cfg, []);
119
+ }
120
+ }
121
+ this.container.removeEventListener("click", this._onClearClick);
122
+ this.isClearClickListenerActive = false;
123
+ }
124
+ updateBoxStyle() {
125
+ const left = Math.min(this.selectionStart.x, this.selectionCurrent.x);
126
+ const top = Math.min(this.selectionStart.y, this.selectionCurrent.y);
127
+ const width = Math.abs(this.selectionCurrent.x - this.selectionStart.x);
128
+ const height = Math.abs(this.selectionCurrent.y - this.selectionStart.y);
129
+ const styleObj = {
130
+ left: `${left}px`,
131
+ top: `${top}px`,
132
+ width: `${width}px`,
133
+ height: `${height}px`
134
+ };
135
+ Object.assign(this.selectionBox.style, styleObj);
136
+ }
137
+ updateSelection() {
138
+ const relLeft = Math.min(this.selectionStart.x, this.selectionCurrent.x);
139
+ const relTop = Math.min(this.selectionStart.y, this.selectionCurrent.y);
140
+ const relWidth = Math.abs(this.selectionCurrent.x - this.selectionStart.x);
141
+ const relHeight = Math.abs(this.selectionCurrent.y - this.selectionStart.y);
142
+ const containerRect = this.container.getBoundingClientRect();
143
+ const boxClientLeft = relLeft - this.container.scrollLeft + containerRect.left;
144
+ const boxClientTop = relTop - this.container.scrollTop + containerRect.top;
145
+ const boxClientRight = boxClientLeft + relWidth;
146
+ const boxClientBottom = boxClientTop + relHeight;
147
+ for (const cfg of this.targets) {
148
+ const candidates = this.resolveCandidates(cfg.selector);
149
+ const selectedForTarget = [];
150
+ for (const el of candidates) {
151
+ const elRect = el.getBoundingClientRect();
152
+ const isIntersecting = !(elRect.right < boxClientLeft || elRect.left > boxClientRight || elRect.bottom < boxClientTop || elRect.top > boxClientBottom);
153
+ if (isIntersecting) selectedForTarget.push(el);
154
+ }
155
+ const lastForTarget = this.targetSelectionCache.get(cfg) ?? [];
156
+ const targetChanged = selectedForTarget.length !== lastForTarget.length || selectedForTarget.some((el, i) => el !== lastForTarget[i]);
157
+ if (targetChanged) {
158
+ this.targetSelectionCache.set(cfg, [ ...selectedForTarget ]);
159
+ if (cfg.onSelectionChange) cfg.onSelectionChange(selectedForTarget);
160
+ }
161
+ }
162
+ }
163
+ enable() {
164
+ this.enabled = true;
165
+ }
166
+ disable() {
167
+ this.enabled = false;
168
+ }
169
+ isEnabled() {
170
+ return this.enabled;
171
+ }
172
+ addTarget(config) {
173
+ this.targets.push({
174
+ selector: config.selector,
175
+ onSelectionChange: config.onSelectionChange,
176
+ onSelectionStart: config.onSelectionStart,
177
+ onSelectionEnd: config.onSelectionEnd,
178
+ onClearClick: config.onClearClick
179
+ });
180
+ }
181
+ destroy() {
182
+ this.container.removeEventListener("mousedown", this._onMouseDown);
183
+ this.container.removeEventListener("click", this._onClearClick);
184
+ window.removeEventListener("mousemove", this._onMouseMove);
185
+ window.removeEventListener("mouseup", this._onMouseUp);
186
+ if (this.selectionBox.parentNode) this.selectionBox.parentNode.removeChild(this.selectionBox);
187
+ }
188
+ resolveCandidates(selector) {
189
+ if (typeof selector === "string") return Array.from(this.container.querySelectorAll(selector));
190
+ if (selector instanceof HTMLElement) return [ selector ];
191
+ if (Array.isArray(selector) && selector.length > 0) {
192
+ if (typeof selector[0] === "string") {
193
+ const combined = selector.join(",");
194
+ return Array.from(this.container.querySelectorAll(combined));
195
+ }
196
+ return selector;
197
+ }
198
+ return [];
199
+ }
200
+ isTextSelectionAllowed(target) {
201
+ for (const selector of this.allowTextSelectionSelectors) if (target.matches(selector) || target.closest(selector)) return true;
202
+ const computedStyle = window.getComputedStyle(target);
203
+ if (computedStyle.userSelect !== "none" && target.childNodes.length > 0) for (const node of Array.from(target.childNodes)) if (node.nodeType === Node.TEXT_NODE && node.textContent?.trim()) return true;
204
+ return false;
205
+ }
206
+ }
207
+
208
+ exports.MarqueeSelector = MarqueeSelector;
209
+
210
+ exports.default = MarqueeSelector;
211
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../src/MarqueeSelector.ts"],"sourcesContent":["export interface MarqueeSelectorOptions {\n /**\n * 容器元素的實例或選擇器字串\n * 這是滑鼠事件監聽的目標,也是計算相對座標的基準。\n */\n container: HTMLElement | string;\n\n /**\n * 允許文字選取的元素選擇器(這些元素上不會啟動框選,可以正常反白複製文字)\n * 例如: ['p', '.text-content', '[contenteditable]']\n * 預設會自動包含: input, textarea, [contenteditable]\n */\n allowTextSelectionOn?: string[];\n}\n\nexport interface MarqueeTargetConfig {\n /**\n * 可以是單一或多個 CSS 選擇器,或直接傳入 HTMLElement(或其陣列)。\n */\n selector: string | string[] | HTMLElement | HTMLElement[];\n\n /**\n * 專屬於此目標的選取變化回調。\n */\n onSelectionChange?: (selectedElements: HTMLElement[]) => void;\n\n /**\n * 框選開始時的回調函式\n * 在滑鼠按下並開始框選時觸發,會傳入點擊的目標元素\n */\n onSelectionStart?: (targetElement: HTMLElement) => void;\n\n /**\n * 框選結束時的回調函式\n * 在滑鼠放開、框選框消失時觸發,會傳入該 target 當前被選取的元素陣列\n */\n onSelectionEnd?: (selectedElements: HTMLElement[]) => void;\n\n /**\n * 當框選結束後,使用者點擊空白處(非該 target 元素)時的回調函式\n * 會傳入當前被選取的元素陣列\n * 使用者可以在此回調中決定是否清除選取、執行其他操作等\n */\n onClearClick?: (selectedElements: HTMLElement[]) => void;\n}\n\nexport default class MarqueeSelector {\n private container: HTMLElement;\n private enabled: boolean;\n private targets: MarqueeTargetConfig[] = [];\n private targetSelectionCache = new WeakMap<\n MarqueeTargetConfig,\n HTMLElement[]\n >();\n private allowTextSelectionSelectors: string[];\n\n private isSelecting: boolean = false;\n private hasStarted: boolean = false; // 追蹤是否真正開始框選(移動過滑鼠)\n private isClearClickListenerActive: boolean = false; // 追蹤清除點擊事件是否已綁定\n private selectionStart: { x: number; y: number } = { x: 0, y: 0 };\n private selectionCurrent: { x: number; y: number } = { x: 0, y: 0 };\n\n private selectionBox: HTMLElement;\n private _onMouseMove = this.onMouseMove.bind(this);\n private _onMouseUp = this.onMouseUp.bind(this);\n private _onMouseDown = this.onMouseDown.bind(this);\n private _onClearClick = this.onClearClick.bind(this);\n\n constructor(options: MarqueeSelectorOptions) {\n // 1. 解析容器\n if (typeof options.container === \"string\") {\n const el = document.querySelector(options.container);\n if (!el) throw new Error(`Container not found: ${options.container}`);\n this.container = el as HTMLElement;\n } else {\n this.container = options.container;\n }\n\n this.enabled = false;\n\n // 預設允許在這些元素上進行文字選取\n const defaultTextSelectors = [\n \"input\",\n \"textarea\",\n \"[contenteditable]\",\n \"pre\",\n \"code\",\n ];\n this.allowTextSelectionSelectors = [\n ...defaultTextSelectors,\n ...(options.allowTextSelectionOn || []),\n ];\n\n // 2. 初始化選取框元素\n this.selectionBox = document.createElement(\"div\");\n this.initSelectionBox();\n\n // 3. 綁定事件\n this.container.addEventListener(\"mousedown\", this._onMouseDown);\n\n // 確保容器定位非 static,以便選取框絕對定位\n const style = window.getComputedStyle(this.container);\n if (style.position === \"static\") {\n this.container.style.position = \"relative\";\n }\n }\n\n private initSelectionBox() {\n // 套用 CSS class\n this.selectionBox.className = \"marquee-selection-box\";\n\n // 將選取框放入容器\n this.container.appendChild(this.selectionBox);\n }\n\n private onMouseDown(event: MouseEvent) {\n if (!this.enabled) return;\n if (event.button !== 0) return; // 僅限左鍵\n\n const target = event.target as HTMLElement;\n\n // 檢查是否應該優先處理文字選取或拖拽(都優先於框選)\n if (\n this.isTextSelectionAllowed(target) ||\n target.draggable ||\n target.closest('[draggable=\"true\"]')\n ) {\n return; // 允許文字選取或拖拽,不啟動框選\n }\n\n // 取得相對於 Container 的座標 (考慮 scroll)\n const rect = this.container.getBoundingClientRect();\n const startX = event.clientX - rect.left + this.container.scrollLeft;\n const startY = event.clientY - rect.top + this.container.scrollTop;\n\n this.selectionStart = { x: startX, y: startY };\n this.selectionCurrent = { x: startX, y: startY };\n\n this.isSelecting = true;\n this.hasStarted = false; // 重置框選開始狀態\n this.updateBoxStyle();\n this.selectionBox.classList.add(\"active\");\n\n // --- 關閉反白機制(user-select: none)---\n document.body.classList.add(\"disable-user-select\");\n\n // 註冊全域監聽\n window.addEventListener(\"mousemove\", this._onMouseMove);\n window.addEventListener(\"mouseup\", this._onMouseUp);\n }\n\n private onMouseMove(event: MouseEvent) {\n if (!this.isSelecting) return;\n\n // 第一次移動時,觸發 onSelectionStart\n if (!this.hasStarted) {\n this.hasStarted = true;\n const target = event.target as HTMLElement;\n for (const cfg of this.targets) {\n if (cfg.onSelectionStart) {\n cfg.onSelectionStart(target);\n }\n }\n }\n\n // 計算當前相對於 Container 的座標\n const rect = this.container.getBoundingClientRect();\n const currentX = event.clientX - rect.left + this.container.scrollLeft;\n const currentY = event.clientY - rect.top + this.container.scrollTop;\n\n this.selectionCurrent = { x: currentX, y: currentY };\n\n this.updateBoxStyle();\n this.updateSelection();\n }\n\n private onMouseUp() {\n if (this.isSelecting) {\n this.isSelecting = false;\n\n this.selectionBox.classList.remove(\"active\");\n\n // 只有當真正開始框選時(有移動過滑鼠)才觸發 onSelectionEnd\n if (this.hasStarted) {\n // 觸發每個 target 的框選結束回調\n for (const cfg of this.targets) {\n if (cfg.onSelectionEnd) {\n const selectedElements = this.targetSelectionCache.get(cfg) ?? [];\n cfg.onSelectionEnd(selectedElements);\n }\n }\n\n // 檢查是否有任何 target 配置了 onClearClick 且有選取項目\n const hasAnyClearClickHandler = this.targets.some((cfg) => {\n if (!cfg.onClearClick) return false;\n const selection = this.targetSelectionCache.get(cfg) ?? [];\n return selection.length > 0;\n });\n\n // 如果有且尚未綁定事件,動態綁定 click 事件\n if (hasAnyClearClickHandler && !this.isClearClickListenerActive) {\n setTimeout(() => {\n this.container.addEventListener(\"click\", this._onClearClick);\n this.isClearClickListenerActive = true;\n }, 0);\n }\n }\n }\n\n window.removeEventListener(\"mousemove\", this._onMouseMove);\n window.removeEventListener(\"mouseup\", this._onMouseUp);\n\n // --- 恢復反白機制(user-select: auto)---\n document.body.classList.remove(\"disable-user-select\");\n }\n\n private onClearClick(event: MouseEvent) {\n const target = event.target as HTMLElement;\n\n // 檢查每個 target,如果註冊了 onClearClick 且有選取項目\n for (const cfg of this.targets) {\n if (!cfg.onClearClick) continue;\n\n const currentSelection = this.targetSelectionCache.get(cfg) ?? [];\n if (currentSelection.length === 0) continue;\n\n // 檢查點擊的元素是否在該 target 的選取範圍內\n const isClickOnTarget = currentSelection.some(\n (el) => el === target || el.contains(target)\n );\n\n // 如果點擊在空白處(不是該 target),觸發 onClearClick 回調\n if (!isClickOnTarget) {\n cfg.onClearClick(currentSelection);\n // 清空該 target 的選取快取,避免重複觸發\n this.targetSelectionCache.set(cfg, []);\n }\n }\n\n // 無論如何都移除事件監聽器(這個監聽器的目的就是等待一次點擊)\n this.container.removeEventListener(\"click\", this._onClearClick);\n this.isClearClickListenerActive = false;\n }\n\n private updateBoxStyle() {\n const left = Math.min(this.selectionStart.x, this.selectionCurrent.x);\n const top = Math.min(this.selectionStart.y, this.selectionCurrent.y);\n const width = Math.abs(this.selectionCurrent.x - this.selectionStart.x);\n const height = Math.abs(this.selectionCurrent.y - this.selectionStart.y);\n\n const styleObj = {\n left: `${left}px`,\n top: `${top}px`,\n width: `${width}px`,\n height: `${height}px`,\n };\n\n // 更新內部元素(即使未 append 也不會報錯)\n Object.assign(this.selectionBox.style, styleObj);\n }\n\n /**\n * 更新選取狀態,執行碰撞檢測\n *\n * 此方法會:\n * 1. 將框選框的相對座標轉換為 Client 座標\n * 2. 遍歷所有 target 的候選元素\n * 3. 使用 AABB 碰撞檢測判斷元素是否與框選框相交\n * 4. 比較新舊選取狀態,若有變化則更新快取並觸發 onSelectionChange 回調\n */\n private updateSelection() {\n // 碰撞檢測需要用 Client Coordinates 比較準確 (因為 getBoundingClientRect 也是 Client Coords)\n // 但我們的 selectionStart/Current 是相對座標。\n // 所以我們要把 Box 轉回 Client 座標,或者把 元素的 Rect 轉成相對座標。\n // 這裡我們把 Box 轉回 Client 座標來跟元素的 getBoundingClientRect 比對。\n\n // 先算出 Box 的相對座標\n const relLeft = Math.min(this.selectionStart.x, this.selectionCurrent.x);\n const relTop = Math.min(this.selectionStart.y, this.selectionCurrent.y);\n const relWidth = Math.abs(this.selectionCurrent.x - this.selectionStart.x);\n const relHeight = Math.abs(this.selectionCurrent.y - this.selectionStart.y);\n\n const containerRect = this.container.getBoundingClientRect();\n\n // 轉換成與 getBoundingClientRect 一致的 Client 座標\n // ClientX = RelX - Scroll + ContainerLeft\n const boxClientLeft =\n relLeft - this.container.scrollLeft + containerRect.left;\n const boxClientTop = relTop - this.container.scrollTop + containerRect.top;\n const boxClientRight = boxClientLeft + relWidth;\n const boxClientBottom = boxClientTop + relHeight;\n\n for (const cfg of this.targets) {\n const candidates = this.resolveCandidates(cfg.selector);\n const selectedForTarget: HTMLElement[] = [];\n\n for (const el of candidates) {\n const elRect = el.getBoundingClientRect();\n const isIntersecting = !(\n elRect.right < boxClientLeft ||\n elRect.left > boxClientRight ||\n elRect.bottom < boxClientTop ||\n elRect.top > boxClientBottom\n );\n\n if (isIntersecting) {\n selectedForTarget.push(el);\n }\n }\n\n const lastForTarget = this.targetSelectionCache.get(cfg) ?? [];\n const targetChanged =\n selectedForTarget.length !== lastForTarget.length ||\n selectedForTarget.some((el, i) => el !== lastForTarget[i]);\n\n if (targetChanged) {\n this.targetSelectionCache.set(cfg, [...selectedForTarget]);\n if (cfg.onSelectionChange) {\n cfg.onSelectionChange(selectedForTarget);\n }\n }\n }\n }\n\n public enable() {\n this.enabled = true;\n }\n\n public disable() {\n this.enabled = false;\n }\n\n public isEnabled() {\n return this.enabled;\n }\n\n // 公開方法:動態新增目標設定\n public addTarget(config: MarqueeTargetConfig) {\n this.targets.push({\n selector: config.selector,\n onSelectionChange: config.onSelectionChange,\n onSelectionStart: config.onSelectionStart,\n onSelectionEnd: config.onSelectionEnd,\n onClearClick: config.onClearClick,\n });\n }\n\n // 公開方法:銷毀\n public destroy() {\n this.container.removeEventListener(\"mousedown\", this._onMouseDown);\n this.container.removeEventListener(\"click\", this._onClearClick);\n window.removeEventListener(\"mousemove\", this._onMouseMove);\n window.removeEventListener(\"mouseup\", this._onMouseUp);\n\n if (this.selectionBox.parentNode) {\n this.selectionBox.parentNode.removeChild(this.selectionBox);\n }\n }\n\n private resolveCandidates(\n selector: string | string[] | HTMLElement | HTMLElement[]\n ): HTMLElement[] {\n if (typeof selector === \"string\") {\n return Array.from(\n this.container.querySelectorAll(selector)\n ) as HTMLElement[];\n }\n\n if (selector instanceof HTMLElement) {\n return [selector];\n }\n\n if (Array.isArray(selector) && selector.length > 0) {\n if (typeof selector[0] === \"string\") {\n const combined = (selector as string[]).join(\",\");\n return Array.from(\n this.container.querySelectorAll(combined)\n ) as HTMLElement[];\n }\n\n return selector as HTMLElement[];\n }\n\n return [];\n }\n\n /**\n * 檢查是否應該允許在目標元素上進行文字選取\n */\n private isTextSelectionAllowed(target: HTMLElement): boolean {\n // 檢查是否匹配允許文字選取的選擇器\n for (const selector of this.allowTextSelectionSelectors) {\n if (target.matches(selector) || target.closest(selector)) {\n return true;\n }\n }\n\n // 檢查元素是否有文字內容且沒有明確禁用 user-select\n const computedStyle = window.getComputedStyle(target);\n if (computedStyle.userSelect !== \"none\" && target.childNodes.length > 0) {\n // 檢查是否包含文字節點\n for (const node of Array.from(target.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE && node.textContent?.trim()) {\n return true;\n }\n }\n }\n\n return false;\n }\n}\n"],"names":["MarqueeSelector","constructor","options","this","targets","targetSelectionCache","WeakMap","isSelecting","hasStarted","isClearClickListenerActive","selectionStart","x","y","selectionCurrent","_onMouseMove","onMouseMove","bind","_onMouseUp","onMouseUp","_onMouseDown","onMouseDown","_onClearClick","onClearClick","container","el","document","querySelector","Error","enabled","defaultTextSelectors","allowTextSelectionSelectors","allowTextSelectionOn","selectionBox","createElement","initSelectionBox","addEventListener","style","window","getComputedStyle","position","className","appendChild","event","button","target","isTextSelectionAllowed","draggable","closest","rect","getBoundingClientRect","startX","clientX","left","scrollLeft","startY","clientY","top","scrollTop","updateBoxStyle","classList","add","body","cfg","onSelectionStart","currentX","currentY","updateSelection","remove","onSelectionEnd","selectedElements","get","hasAnyClearClickHandler","some","selection","length","setTimeout","removeEventListener","currentSelection","isClickOnTarget","contains","set","Math","min","width","abs","height","styleObj","Object","assign","relLeft","relTop","relWidth","relHeight","containerRect","boxClientLeft","boxClientTop","boxClientRight","boxClientBottom","candidates","resolveCandidates","selector","selectedForTarget","elRect","isIntersecting","right","bottom","push","lastForTarget","targetChanged","i","onSelectionChange","enable","disable","isEnabled","addTarget","config","destroy","parentNode","removeChild","Array","from","querySelectorAll","HTMLElement","isArray","combined","join","matches","computedStyle","userSelect","childNodes","node","nodeType","Node","TEXT_NODE","textContent","trim"],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import "./marquee-selector.css";
2
+ export { default as MarqueeSelector } from "./MarqueeSelector";
3
+ export { default } from "./MarqueeSelector";
4
+ export type { MarqueeSelectorOptions, MarqueeTargetConfig, } from "./MarqueeSelector";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,wBAAwB,CAAC;AAEhC,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,YAAY,EACV,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,mBAAmB,CAAC"}