marquee-selection 0.0.11

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/index.html ADDED
@@ -0,0 +1,740 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>圈选DOM</title>
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <style>
10
+ body {
11
+ font-family: Arial, sans-serif;
12
+ margin: 20px;
13
+ }
14
+
15
+ #container {
16
+ width: 100vw;
17
+ height: 100vh;
18
+ border: 1px solid #ccc;
19
+ position: relative;
20
+ user-select: none;
21
+ /* visibility: hidden; */
22
+ }
23
+
24
+ .item {
25
+ display: inline-block;
26
+ width: 100px;
27
+ height: 100px;
28
+ margin: 10px;
29
+ background-color: #007bff;
30
+ color: white;
31
+ text-align: center;
32
+ line-height: 100px;
33
+ cursor: pointer;
34
+ }
35
+
36
+ /* 圈选后高亮。此类名与 src/index.ts 中 selectedClass 默认为 selected 对应 */
37
+ .selected {
38
+ outline: 1px solid #268aff;
39
+ /* outline-offset: 1px; */
40
+ /* box-shadow: 0 0 0 3px rgba(38, 138, 255, 0.2); */
41
+ /* border-radius: 4px; */
42
+ }
43
+
44
+ /* 悬浮高亮样式 */
45
+ .hovered {
46
+ outline: 1px solid rgba(38, 138, 255, 0.7);
47
+ outline-style: dashed;
48
+ background-color: rgba(38, 138, 255, 0.12);
49
+ /* 更浅的投影作为提示 */
50
+ /* box-shadow: 0 0 0 2px rgba(38, 138, 255, 0.12); */
51
+ }
52
+
53
+ /* 结果面板 */
54
+ #selection-panel {
55
+ position: fixed;
56
+ right: 16px;
57
+ top: 16px;
58
+ width: 350px;
59
+ max-height: 60vh;
60
+ overflow: auto;
61
+ background: #fff;
62
+ border: 1px solid #e5e7eb;
63
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
64
+ border-radius: 8px;
65
+ padding: 10px 12px;
66
+ font-size: 12px;
67
+ line-height: 1.4;
68
+ color: #111827;
69
+ z-index: 2147483647;
70
+ }
71
+
72
+ #selection-panel .panel-title {
73
+ font-weight: 600;
74
+ margin-bottom: 8px;
75
+ }
76
+
77
+ #selection-panel .group-row {
78
+ display: flex;
79
+ justify-content: space-between;
80
+ padding: 4px 0;
81
+ border-bottom: 1px dashed #eee;
82
+ }
83
+
84
+ #selection-panel .muted {
85
+ color: #6b7280;
86
+ }
87
+
88
+ /* 树状列表样式 */
89
+ .tree {
90
+ margin: 6px 0 8px;
91
+ padding-left: 12px;
92
+ }
93
+
94
+ .tree ul {
95
+ list-style: none;
96
+ margin: 0;
97
+ padding-left: 14px;
98
+ border-left: 1px dashed #e5e7eb;
99
+ }
100
+
101
+ .tree li {
102
+ margin: 2px 0;
103
+ padding-left: 6px;
104
+ position: relative;
105
+ }
106
+
107
+ .tree li::before {
108
+ content: "";
109
+ position: absolute;
110
+ left: -12px;
111
+ top: 10px;
112
+ width: 12px;
113
+ height: 1px;
114
+ background: #e5e7eb;
115
+ }
116
+
117
+ .node-label {
118
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
119
+ font-size: 11px;
120
+ color: #374151;
121
+ }
122
+
123
+ details.group {
124
+ border: 1px solid #f3f4f6;
125
+ border-radius: 6px;
126
+ margin: 8px 0;
127
+ }
128
+
129
+ details.group details.group {
130
+ margin: 8px;
131
+ }
132
+
133
+ details.group>summary {
134
+ cursor: pointer;
135
+ padding: 6px 8px;
136
+ background: #f9fafb;
137
+ }
138
+
139
+ details.group[open]>summary {
140
+ background: #f3f4f6;
141
+ }
142
+
143
+ .node-content {
144
+ margin-left: 4px;
145
+ /* tag */
146
+ font-size: 10px;
147
+ color: #6b7280;
148
+ background: #e0e7ff;
149
+ padding: 1px 4px;
150
+ border-radius: 4px;
151
+ }
152
+ </style>
153
+ </head>
154
+
155
+ <body>
156
+ <div id="container">
157
+ <div style="position: relative; width: 674px; height: 796px; overflow: hidden">
158
+ <div style=" position: absolute; z-index: 1; left: 0px; top: 0px; font-size: 34px; font-family: &quot;PingFang SC&quot;; font-weight: 600; line-height: 47.6px; color: rgb(26, 26, 26); justify-content: left; align-items: flex-start; height: 48px; text-align: left; width: 136px; white-space: nowrap; "
159
+ data-id="1332:4545" data-name="专属推荐" data-type="TEXT"> 专属推荐 </div><img
160
+ src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/d97ea177-fc53-414e-b12c-a4cfe04992fa"
161
+ style="position: absolute; z-index: 3; left: 550px; top: 11px; width: 28px; height: 25px"
162
+ data-id="1332:4547" data-name="形状" data-type="ICON">
163
+ <div style=" position: absolute; z-index: 4; left: 592px; top: 6px; font-size: 26px; font-family: &quot;PingFang SC&quot;; font-weight: 400; line-height: 36.4px; color: rgb(140, 140, 140); justify-content: left; align-items: flex-start; height: 36px; text-align: left; width: 78px; white-space: nowrap; "
164
+ data-id="1332:4548" data-name="换一批" data-type="TEXT"> 换一批 </div>
165
+ <div style=" position: absolute; z-index: 6; left: 0px; top: 304px; font-size: 24px; font-family: &quot;PingFang SC&quot;; font-weight: 400; line-height: 26px; color: rgb(35, 35, 35); justify-content: left; align-items: flex-start; height: 26px; text-align: left; width: 187px; white-space: nowrap; "
166
+ data-id="1332:4550" data-name="Brooklyn Simm..." data-type="TEXT"> Brooklyn Simm... </div>
167
+ <div style=" position: absolute; z-index: 7; left: 0px; top: 342px; font-size: 22px; font-family: &quot;PingFang SC&quot;; font-weight: 400; line-height: 30.8px; color: rgb(140, 140, 140); justify-content: left; align-items: flex-start; height: 31px; text-align: left; width: 156px; white-space: nowrap; "
168
+ data-id="1332:4551" data-name="“你关注了歌手”" data-type="TEXT"> “你关注了歌手” </div><img
169
+ src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/7b54ff4c-4a31-4f62-9bc1-0a9f5e14877a"
170
+ style=" position: absolute; z-index: 9; left: 0px; top: 68px; width: 216px; height: 216px; border-radius: 16px; "
171
+ data-id="1332:4553" data-name="Rectangle 6187" data-type="IMG">
172
+ <div style=" position: absolute; z-index: 10; left: 164px; top: 81px; width: 40px; height: 40px; overflow: hidden; filter: drop-shadow(0px 4px 18px rgba(0, 0, 0, 0.15)); "
173
+ data-id="1332:4554" data-name="Frame 427323199" data-type="CONTAINER"></div><img
174
+ src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/e81a7e4e-1b2d-4ba6-ad03-ab9c6593b7a0"
175
+ style="position: absolute; z-index: 11; left: 168px; top: 84px; width: 32px; height: 30px"
176
+ data-id="1332:4555" data-name="Star 1" data-type="ICON"><img
177
+ src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/7331967a-7f98-400b-8f39-da98073801ea"
178
+ style="position: absolute; z-index: 12; left: 0px; top: 244px; width: 187px; height: 40px"
179
+ data-id="1332:4556" data-name="Group 44753" data-type="IMG">
180
+ <div style=" position: absolute; z-index: 13; left: 50px; top: 250px; font-size: 22px; font-family: &quot;PingFang SC&quot;; font-weight: 400; line-height: 30.8px; color: rgb(255, 255, 255); justify-content: left; align-items: flex-start; height: 31px; text-align: left; width: 125px; "
181
+ data-id="1332:4561" data-name="王嘉尔 | 邓..." data-type="TEXT"> 王嘉尔 | 邓... </div>
182
+ <div style=" position: absolute; z-index: 15; left: 0px; top: 380px; width: 116px; height: 30px; border-radius: 8px; background-color: rgb(255, 242, 233); "
183
+ data-id="1332:4563" data-name="Rectangle 540" data-type="CONTAINER"></div>
184
+ <div style=" position: absolute; z-index: 16; left: 8px; top: 381px; font-size: 20px; font-family: &quot;PingFang SC&quot;; font-weight: 400; line-height: 28px; color: rgb(255, 124, 36); justify-content: left; align-items: flex-start; height: 28px; text-align: left; width: 100px; white-space: nowrap; "
185
+ data-id="1332:4564" data-name="翻唱获收益" data-type="TEXT"> 翻唱获收益 </div>
186
+ <div style=" position: absolute; z-index: 18; left: 0px; top: 688px; font-size: 24px; font-family: &quot;PingFang SC&quot;; font-weight: 400; line-height: 26px; color: rgb(35, 35, 35); justify-content: left; align-items: flex-start; height: 26px; text-align: left; width: 187px; white-space: nowrap; "
187
+ data-id="1332:4566" data-name="Brooklyn Simm..." data-type="TEXT"> Brooklyn Simm... </div>
188
+ <div style=" position: absolute; z-index: 19; left: 0px; top: 726px; font-size: 22px; font-family: &quot;PingFang SC&quot;; font-weight: 400; line-height: 30.8px; color: rgb(140, 140, 140); justify-content: left; align-items: flex-start; height: 31px; text-align: left; width: 156px; white-space: nowrap; "
189
+ data-id="1332:4567" data-name="“你关注了歌手”" data-type="TEXT"> “你关注了歌手” </div><img
190
+ src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/77fef8ae-6c22-4428-a552-12ab3d5d0c92"
191
+ style=" position: absolute; z-index: 21; left: 0px; top: 452px; width: 216px; height: 216px; border-radius: 16px; "
192
+ data-id="1332:4569" data-name="Rectangle 6187" data-type="IMG">
193
+ <div style=" position: absolute; z-index: 22; left: 164px; top: 464px; width: 40px; height: 40px; overflow: hidden; filter: drop-shadow(0px 4px 18px rgba(0, 0, 0, 0.15)); "
194
+ data-id="1332:4570" data-name="Frame 427323199" data-type="CONTAINER"></div><img
195
+ src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/e254caf8-dc51-4a88-a193-0179b2145bdf"
196
+ style="position: absolute; z-index: 23; left: 168px; top: 467px; width: 32px; height: 30px"
197
+ data-id="1332:4571" data-name="Star 1" data-type="ICON"><img
198
+ src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/9c4d7c17-1b61-4c9c-bf8e-383439c3e9a5"
199
+ style="position: absolute; z-index: 24; left: 0px; top: 628px; width: 187px; height: 40px"
200
+ data-id="1332:4572" data-name="Group 44753" data-type="IMG">
201
+ <div style=" position: absolute; z-index: 25; left: 50px; top: 634px; font-size: 22px; font-family: &quot;PingFang SC&quot;; font-weight: 400; line-height: 30.8px; color: rgb(255, 255, 255); justify-content: left; align-items: flex-start; height: 31px; text-align: left; width: 131px; "
202
+ data-id="1332:4577" data-name="王嘉尔 | 邓..." data-type="TEXT"> 王嘉尔 | 邓... </div>
203
+ <div style=" position: absolute; z-index: 27; left: 0px; top: 764px; width: 116px; height: 30px; border-radius: 8px; background-color: rgb(255, 242, 233); "
204
+ data-id="1332:4579" data-name="Rectangle 540" data-type="CONTAINER"></div>
205
+ <div style=" position: absolute; z-index: 28; left: 8px; top: 765px; font-size: 20px; font-family: &quot;PingFang SC&quot;; font-weight: 400; line-height: 28px; color: rgb(255, 124, 36); justify-content: left; align-items: flex-start; height: 28px; text-align: left; width: 100px; white-space: nowrap; "
206
+ data-id="1332:4580" data-name="翻唱获收益" data-type="TEXT"> 翻唱获收益 </div>
207
+ <div style=" position: absolute; z-index: 30; left: 457px; top: 304px; font-size: 24px; font-family: &quot;PingFang SC&quot;; font-weight: 400; line-height: 26px; color: rgb(35, 35, 35); justify-content: left; align-items: flex-start; height: 26px; text-align: left; width: 187px; white-space: nowrap; "
208
+ data-id="1332:4582" data-name="Brooklyn Simm..." data-type="TEXT"> Brooklyn Simm... </div>
209
+ <div style=" position: absolute; z-index: 31; left: 457px; top: 342px; font-size: 24px; font-family: &quot;PingFang SC&quot;; font-weight: 400; line-height: 33.6px; color: rgb(140, 140, 140); justify-content: left; align-items: flex-start; height: 34px; text-align: left; width: 203px; white-space: nowrap; "
210
+ data-id="1332:4583" data-name="“你唱过的歌手”" data-type="TEXT"> “你唱过的歌手” </div><img
211
+ src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/9e211cfc-a225-4c05-bdd0-ab0d112e15be"
212
+ style=" position: absolute; z-index: 32; left: 457px; top: 69px; width: 216px; height: 216px; border-radius: 20px; "
213
+ data-id="1332:4584" data-name="Rectangle 6189" data-type="IMG"><img
214
+ src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/dcf36dca-9c50-4f94-bd6b-0222693c1d0d"
215
+ style="position: absolute; z-index: 34; left: 458px; top: 244px; width: 115px; height: 40px"
216
+ data-id="1332:4586" data-name="Rectangle 1284" data-type="IMG">
217
+ <div style=" position: absolute; z-index: 35; left: 508px; top: 248px; font-size: 22px; font-family: &quot;PingFang SC&quot;; font-weight: 400; line-height: 30.8px; color: rgb(255, 255, 255); justify-content: left; align-items: flex-start; height: 31px; text-align: left; width: 50px; white-space: nowrap; "
218
+ data-id="1332:4587" data-name="Ailee" data-type="TEXT"> Ailee </div><img
219
+ src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/0a4a4918-0ac0-48bd-a1d3-3e18676a09a2"
220
+ style="position: absolute; z-index: 38; left: 483px; top: 256px; width: 14px; height: 15px"
221
+ data-id="1332:4590" data-name="Polygon 3" data-type="ICON">
222
+ <div style=" position: absolute; z-index: 39; left: 621px; top: 81px; width: 40px; height: 40px; overflow: hidden; filter: drop-shadow(0px 4px 18px rgba(0, 0, 0, 0.15)); "
223
+ data-id="1332:4591" data-name="Frame 340" data-type="CONTAINER"></div><img
224
+ src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/7d4add74-87c4-4469-af90-dc49a3d10901"
225
+ style="position: absolute; z-index: 40; left: 625px; top: 84px; width: 32px; height: 30px"
226
+ data-id="1332:4592" data-name="Star 1" data-type="ICON">
227
+ <div style=" position: absolute; z-index: 42; left: 457px; top: 688px; font-size: 24px; font-family: &quot;PingFang SC&quot;; font-weight: 400; line-height: 26px; color: rgb(35, 35, 35); justify-content: left; align-items: flex-start; height: 26px; text-align: left; width: 187px; white-space: nowrap; "
228
+ data-id="1332:4594" data-name="Brooklyn Simm..." data-type="TEXT"> Brooklyn Simm... </div>
229
+ <div style=" position: absolute; z-index: 43; left: 457px; top: 726px; font-size: 24px; font-family: &quot;PingFang SC&quot;; font-weight: 400; line-height: 33.6px; color: rgb(140, 140, 140); justify-content: left; align-items: flex-start; height: 34px; text-align: left; width: 203px; white-space: nowrap; "
230
+ data-id="1332:4595" data-name="“你唱过的歌手”" data-type="TEXT"> “你唱过的歌手” </div><img
231
+ src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/b646c719-2c10-4a27-984b-96c2291f81a5"
232
+ style=" position: absolute; z-index: 44; left: 457px; top: 452px; width: 216px; height: 216px; border-radius: 20px; "
233
+ data-id="1332:4596" data-name="Rectangle 6189" data-type="IMG"><img
234
+ src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/b5de5fd9-b2d0-4d2d-8f0e-7283044f4d8b"
235
+ style="position: absolute; z-index: 46; left: 458px; top: 627px; width: 115px; height: 40px"
236
+ data-id="1332:4598" data-name="Rectangle 1284" data-type="IMG">
237
+ <div style=" position: absolute; z-index: 47; left: 508px; top: 631px; font-size: 22px; font-family: &quot;PingFang SC&quot;; font-weight: 400; line-height: 30.8px; color: rgb(255, 255, 255); justify-content: left; align-items: flex-start; height: 31px; text-align: left; width: 50px; white-space: nowrap; "
238
+ data-id="1332:4599" data-name="Ailee" data-type="TEXT"> Ailee </div><img
239
+ src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/b532516a-c698-4667-8fdf-285a091df1a4"
240
+ style="position: absolute; z-index: 50; left: 483px; top: 639px; width: 14px; height: 15px"
241
+ data-id="1332:4602" data-name="Polygon 3" data-type="ICON">
242
+ <div style=" position: absolute; z-index: 51; left: 621px; top: 464px; width: 40px; height: 40px; overflow: hidden; filter: drop-shadow(0px 4px 18px rgba(0, 0, 0, 0.15)); "
243
+ data-id="1332:4603" data-name="Frame 340" data-type="CONTAINER"></div><img
244
+ src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/0fdb105d-057c-4e2c-824d-b6dce362acd6"
245
+ style="position: absolute; z-index: 52; left: 625px; top: 467px; width: 32px; height: 30px"
246
+ data-id="1332:4604" data-name="Star 1" data-type="ICON">
247
+ <div style=" position: absolute; z-index: 54; left: 229px; top: 304px; font-size: 24px; font-family: &quot;PingFang SC&quot;; font-weight: 400; line-height: 26px; color: rgb(35, 35, 35); justify-content: left; align-items: flex-start; height: 26px; text-align: left; width: 187px; white-space: nowrap; "
248
+ data-id="1332:4606" data-name="Brooklyn Simm..." data-type="TEXT"> Brooklyn Simm... </div>
249
+ <div style=" position: absolute; z-index: 55; left: 229px; top: 342px; font-size: 22px; font-family: &quot;PingFang SC&quot;; font-weight: 400; line-height: 30.8px; color: rgb(140, 140, 140); justify-content: left; align-items: flex-start; height: 31px; text-align: left; width: 222px; white-space: nowrap; "
250
+ data-id="1332:4607" data-name="“热门歌曲飙升第一”" data-type="TEXT"> “热门歌曲飙升第一” </div>
251
+ <div style=" position: absolute; z-index: 57; left: 228px; top: 381px; width: 116px; height: 30px; border-radius: 8px; background-color: rgb(255, 242, 233); "
252
+ data-id="1332:4609" data-name="Rectangle 540" data-type="CONTAINER"></div>
253
+ <div style=" position: absolute; z-index: 58; left: 236px; top: 382px; font-size: 20px; font-family: &quot;PingFang SC&quot;; font-weight: 400; line-height: 28px; color: rgb(255, 124, 36); justify-content: left; align-items: flex-start; height: 28px; text-align: left; width: 100px; white-space: nowrap; "
254
+ data-id="1332:4610" data-name="翻唱获收益" data-type="TEXT"> 翻唱获收益 </div><img
255
+ src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/080ab0b9-97dc-4347-ab1a-7c52efc3a1da"
256
+ style=" position: absolute; z-index: 59; left: 229px; top: 68px; width: 216px; height: 216px; border-radius: 20px; "
257
+ data-id="1332:4611" data-name="Rectangle 6188" data-type="IMG"><img
258
+ src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/8b626f81-832f-4e19-901d-3df7186b98f0"
259
+ style="position: absolute; z-index: 61; left: 229px; top: 244px; width: 115px; height: 40px"
260
+ data-id="1332:4613" data-name="Rectangle 1284" data-type="IMG">
261
+ <div style=" position: absolute; z-index: 62; left: 279px; top: 248px; font-size: 22px; font-family: &quot;PingFang SC&quot;; font-weight: 400; line-height: 30.8px; color: rgb(255, 255, 255); justify-content: left; align-items: flex-start; height: 31px; text-align: left; width: 50px; white-space: nowrap; "
262
+ data-id="1332:4614" data-name="Ailee" data-type="TEXT"> Ailee </div><img
263
+ src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/9bfc76e6-c043-4d44-bbd9-8b25b82c3087"
264
+ style="position: absolute; z-index: 65; left: 254px; top: 256px; width: 14px; height: 15px"
265
+ data-id="1332:4617" data-name="Polygon 3" data-type="ICON">
266
+ <div style=" position: absolute; z-index: 66; left: 393px; top: 81px; width: 40px; height: 40px; overflow: hidden; filter: drop-shadow(0px 4px 18px rgba(0, 0, 0, 0.15)); "
267
+ data-id="1332:4618" data-name="Frame 427323199" data-type="CONTAINER"></div><img
268
+ src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/33772b18-02e2-48e4-8dbc-01e37286d366"
269
+ style="position: absolute; z-index: 67; left: 397px; top: 84px; width: 32px; height: 30px"
270
+ data-id="1332:4619" data-name="Star 1" data-type="ICON">
271
+ <div style=" position: absolute; z-index: 69; left: 229px; top: 688px; font-size: 24px; font-family: &quot;PingFang SC&quot;; font-weight: 400; line-height: 26px; color: rgb(35, 35, 35); justify-content: left; align-items: flex-start; height: 26px; text-align: left; width: 187px; white-space: nowrap; "
272
+ data-id="1332:4621" data-name="Brooklyn Simm..." data-type="TEXT"> Brooklyn Simm... </div>
273
+ <div style=" position: absolute; z-index: 70; left: 229px; top: 726px; font-size: 22px; font-family: &quot;PingFang SC&quot;; font-weight: 400; line-height: 30.8px; color: rgb(140, 140, 140); justify-content: left; align-items: flex-start; height: 31px; text-align: left; width: 222px; white-space: nowrap; "
274
+ data-id="1332:4622" data-name="“热门歌曲飙升第一”" data-type="TEXT"> “热门歌曲飙升第一” </div>
275
+ <div style=" position: absolute; z-index: 72; left: 228px; top: 765px; width: 116px; height: 30px; border-radius: 8px; background-color: rgb(255, 242, 233); "
276
+ data-id="1332:4624" data-name="Rectangle 540" data-type="CONTAINER"></div>
277
+ <div style=" position: absolute; z-index: 73; left: 236px; top: 766px; font-size: 20px; font-family: &quot;PingFang SC&quot;; font-weight: 400; line-height: 28px; color: rgb(255, 124, 36); justify-content: left; align-items: flex-start; height: 28px; text-align: left; width: 100px; white-space: nowrap; "
278
+ data-id="1332:4625" data-name="翻唱获收益" data-type="TEXT"> 翻唱获收益 </div><img
279
+ src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/cb33ef76-bff0-430b-b437-01231b928289"
280
+ style=" position: absolute; z-index: 74; left: 229px; top: 452px; width: 216px; height: 216px; border-radius: 20px; "
281
+ data-id="1332:4626" data-name="Rectangle 6188" data-type="IMG"><img
282
+ src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/98a53c9f-0c6f-43b1-8399-f3df1b4d59a3"
283
+ style="position: absolute; z-index: 76; left: 229px; top: 628px; width: 115px; height: 40px"
284
+ data-id="1332:4628" data-name="Rectangle 1284" data-type="IMG">
285
+ <div style=" position: absolute; z-index: 77; left: 279px; top: 632px; font-size: 22px; font-family: &quot;PingFang SC&quot;; font-weight: 400; line-height: 30.8px; color: rgb(255, 255, 255); justify-content: left; align-items: flex-start; height: 31px; text-align: left; width: 50px; white-space: nowrap; "
286
+ data-id="1332:4629" data-name="Ailee" data-type="TEXT"> Ailee </div><img
287
+ src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/028cfc63-d000-4731-8700-91df61cda7fd"
288
+ style="position: absolute; z-index: 80; left: 254px; top: 639px; width: 14px; height: 15px"
289
+ data-id="1332:4632" data-name="Polygon 3" data-type="ICON">
290
+ <div style=" position: absolute; z-index: 81; left: 393px; top: 464px; width: 40px; height: 40px; overflow: hidden; filter: drop-shadow(0px 4px 18px rgba(0, 0, 0, 0.15)); "
291
+ data-id="1332:4633" data-name="Frame 427323199" data-type="CONTAINER"></div><img
292
+ src="https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/23e44c65-c515-4d29-96f6-8470068f49a4"
293
+ style="position: absolute; z-index: 82; left: 397px; top: 467px; width: 32px; height: 30px"
294
+ data-id="1332:4634" data-name="Star 1" data-type="ICON">
295
+ </div>
296
+
297
+ </div>
298
+ <div id="selection-panel" style="display:none">
299
+ <div class="panel-title">圈选结果</div>
300
+ <div id="selection-panel-toolbar" style="display:flex;gap:8px;align-items:center;margin-bottom:6px;">
301
+ <button id="btn-toggle-nested"
302
+ style="padding:2px 8px;border:1px solid #e5e7eb;border-radius:6px;background:#f9fafb;cursor:pointer;">
303
+ 组嵌套查看
304
+ </button>
305
+ </div>
306
+ <div id="selection-panel-content" class="muted">暂无</div>
307
+ </div>
308
+ <div>
309
+ <input id="group-selector-input" type="text" placeholder="CSS 选择器,如 img 或 [data-type='IMG']"
310
+ style="flex:1;min-width:120px;padding:4px 8px;border:1px solid #e5e7eb;border-radius:6px;outline:none;" />
311
+ <button id="btn-add-group-by-selector"
312
+ style="padding:4px 10px;border:1px solid #10b981;border-radius:6px;background:#10b981;color:#fff;cursor:pointer;">
313
+ 成组
314
+ </button>
315
+ </div>
316
+ <script type="module">
317
+ import { InfiniteCanvas, marqueeSelection } from '/src/index.ts'
318
+ // import { InfiniteCanvas } from './dist/infinite-canvas.es.js'
319
+ // import { marqueeSelection } from './dist/marquee-selection.es.js'
320
+
321
+ const container = document.getElementById('container')
322
+ // 初始化无限画布(双指缩放/平移)
323
+ // if (container) {
324
+ // const canvas = new InfiniteCanvas({
325
+ // container,
326
+ // initialScale: 1,
327
+ // minScale: 0.1,
328
+ // maxScale: 6,
329
+ // contentClassName: 'stage-content',
330
+ // // 初始化时让内容完整可见且居中(也可改为 cover 铺满)
331
+ // // autoFit: { mode: 'contain', alignX: 'center', alignY: 'center', padding: 0 },
332
+ // // autoFit: { mode: 'cover', alignX: 'center', alignY: 'top' },
333
+ // // autoRefitOnResize: true,
334
+ // onReady: (container) => {
335
+ // console.log('InfiniteCanvas is ready:', container);
336
+ // container.style.visibility = 'visible'
337
+ // }
338
+ // ,
339
+ // // 使用回调替代事件监听:开始时隐藏覆盖层,结束时显示并刷新
340
+ // onTransformStart: () => {
341
+ // // @ts-ignore
342
+ // if (window.ctrl && typeof window.ctrl.hideOverlays === 'function') {
343
+ // // @ts-ignore
344
+ // window.ctrl.hideOverlays()
345
+ // }
346
+ // },
347
+ // onTransformEnd: () => {
348
+ // // @ts-ignore
349
+ // if (window.ctrl && typeof window.ctrl.showAndRefreshOverlays === 'function') {
350
+ // // @ts-ignore
351
+ // window.ctrl.showAndRefreshOverlays()
352
+ // }
353
+ // }
354
+ // })
355
+ // }
356
+ let nestedMode = false
357
+ let lastSnapshot = null
358
+ // AI 悬浮层层级管理
359
+ let aiZ = 2147492000
360
+ // 可拖动 AI 悬浮层(无遮罩)
361
+ const openAIDialog = (ctx) => {
362
+ const groupIdx = (typeof ctx?.index === 'number' && ctx.index >= 0) ? ctx.index : null
363
+ const panelId = groupIdx !== null ? `ai-dialog-${groupIdx}` : `ai-dialog-${Date.now()}`
364
+ // 若该组已存在面板,直接置顶与聚焦
365
+ let panel = document.getElementById(panelId)
366
+ if (panel) {
367
+ panel.style.zIndex = String(++aiZ)
368
+ const ta = panel.querySelector('textarea')
369
+ ta && ta.focus()
370
+ return
371
+ }
372
+ panel = document.createElement('div')
373
+ panel.id = panelId
374
+ panel.className = 'ai-dialog'
375
+ panel.style.position = 'fixed'
376
+ panel.style.left = '0'
377
+ panel.style.top = '0'
378
+ panel.style.width = '560px'
379
+ panel.style.maxWidth = '92vw'
380
+ panel.style.background = '#fff'
381
+ panel.style.border = '1px solid #e5e7eb'
382
+ panel.style.borderRadius = '10px'
383
+ panel.style.boxShadow = '0 12px 28px rgba(0,0,0,0.18)'
384
+ panel.style.padding = '12px'
385
+ panel.style.fontSize = '14px'
386
+ panel.style.color = '#111827'
387
+ panel.style.display = 'flex'
388
+ panel.style.flexDirection = 'column'
389
+ panel.style.gap = '10px'
390
+ panel.style.zIndex = String(++aiZ)
391
+ panel.style.cursor = 'default'
392
+
393
+ // 标题栏(用于拖拽)
394
+ const head = document.createElement('div')
395
+ head.style.display = 'flex'
396
+ head.style.alignItems = 'center'
397
+ head.style.justifyContent = 'space-between'
398
+ head.style.userSelect = 'none'
399
+ head.style.cursor = 'move'
400
+ const title = document.createElement('div')
401
+ const idx = typeof ctx?.index === 'number' ? ctx.index + 1 : '-'
402
+ const count = (ctx?.group || []).length
403
+ title.textContent = `AI 对话 · 组 ${idx}(元素 ${count})`
404
+ title.style.fontWeight = '600'
405
+ const closeBtn = document.createElement('button')
406
+ closeBtn.textContent = '×'
407
+ closeBtn.style.fontSize = '18px'
408
+ closeBtn.style.lineHeight = '18px'
409
+ closeBtn.style.width = '28px'
410
+ closeBtn.style.height = '28px'
411
+ closeBtn.style.border = 'none'
412
+ closeBtn.style.borderRadius = '6px'
413
+ closeBtn.style.background = 'transparent'
414
+ closeBtn.style.cursor = 'pointer'
415
+ closeBtn.onclick = () => panel.remove()
416
+ head.appendChild(title)
417
+ head.appendChild(closeBtn)
418
+
419
+ const ta = document.createElement('textarea')
420
+ ta.placeholder = '请输入 Prompt(例如:总结这些元素的文本内容,或生成描述)\n(Cmd/Ctrl+Enter 发送)'
421
+ ta.style.width = '100%'
422
+ ta.style.minHeight = '110px'
423
+ ta.style.padding = '8px 10px'
424
+ ta.style.border = '1px solid #e5e7eb'
425
+ ta.style.borderRadius = '8px'
426
+ ta.style.outline = 'none'
427
+ ta.addEventListener('keydown', (e) => {
428
+ if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') submit()
429
+ })
430
+
431
+ const footer = document.createElement('div')
432
+ footer.style.display = 'flex'
433
+ footer.style.justifyContent = 'flex-end'
434
+ footer.style.gap = '8px'
435
+ const send = document.createElement('button')
436
+ send.textContent = '发送'
437
+ send.style.padding = '6px 12px'
438
+ send.style.border = '1px solid #2563eb'
439
+ send.style.background = '#2563eb'
440
+ send.style.color = '#fff'
441
+ send.style.borderRadius = '8px'
442
+ send.style.cursor = 'pointer'
443
+ const submit = () => {
444
+ const prompt = ta.value.trim()
445
+ const snapshot = ctx?.getSnapshot ? ctx.getSnapshot() : null
446
+ console.log('[AI] prompt:', prompt, { groupIndex: ctx?.index, group: ctx?.group, snapshot })
447
+ // TODO: 在此处对接你的 AI 服务
448
+ // panel.remove() // 若发送后需要关闭,可取消注释
449
+ }
450
+ send.onclick = submit
451
+ footer.appendChild(send)
452
+
453
+ panel.appendChild(head)
454
+ panel.appendChild(ta)
455
+ panel.appendChild(footer)
456
+ document.body.appendChild(panel)
457
+
458
+ // 跟随时的基础偏移(相对锚点),用于保持用户拖拽后的相对位置
459
+ const pad = 10
460
+ let deltaX = 0, deltaY = 0
461
+ let initialOriginX = null, initialOriginY = null
462
+
463
+ // 计算锚点矩形:优先使用组外接框(实时快照),其次 toolbar 元素,最后初次 anchorRect
464
+ const getAnchorRect = () => {
465
+ const idx = (typeof ctx?.index === 'number' && ctx.index >= 0) ? ctx.index : null
466
+ if (idx !== null && ctx?.getSnapshot) {
467
+ const snap = ctx.getSnapshot()
468
+ if (snap && snap.type === 'groups') {
469
+ const r = snap.groupRects[idx]
470
+ if (r) return { left: r.left, top: r.top, right: r.left + r.width, bottom: r.top + r.height }
471
+ }
472
+ }
473
+ if (ctx?.anchorEl) {
474
+ const r = ctx.anchorEl.getBoundingClientRect()
475
+ return { left: r.left, top: r.top, right: r.right, bottom: r.bottom }
476
+ }
477
+ if (ctx?.anchorRect) {
478
+ const r = ctx.anchorRect
479
+ return { left: r.left, top: r.top, right: r.right, bottom: r.bottom }
480
+ }
481
+ return null
482
+ }
483
+ // 初始位置:基于锚点/鼠标定位,避免出屏
484
+ const placeInitial = () => {
485
+ const w = panel.offsetWidth
486
+ const h = panel.offsetHeight
487
+ let px = typeof ctx?.mouseX === 'number' ? ctx.mouseX + pad : window.innerWidth - w - 20
488
+ let py = typeof ctx?.mouseY === 'number' ? ctx.mouseY + pad : 20
489
+ const ar = getAnchorRect()
490
+ if (ar) { px = ar.right + pad; py = Math.max(pad, ar.bottom + pad) }
491
+ // 视口内裁剪
492
+ px = Math.min(Math.max(pad, px), window.innerWidth - w - pad)
493
+ py = Math.min(Math.max(pad, py), window.innerHeight - h - pad)
494
+ panel.style.left = px + 'px'
495
+ panel.style.top = py + 'px'
496
+ // 初始化相对锚点的偏移(作为滚动跟随的持久 delta)
497
+ if (ar) { initialOriginX = ar.right + pad; initialOriginY = ar.bottom + pad }
498
+ else if (typeof ctx?.mouseX === 'number' && typeof ctx?.mouseY === 'number') { initialOriginX = ctx.mouseX + pad; initialOriginY = ctx.mouseY + pad }
499
+ else { initialOriginX = px; initialOriginY = py }
500
+ deltaX = px - initialOriginX
501
+ deltaY = py - initialOriginY
502
+ }
503
+ placeInitial()
504
+ setTimeout(() => ta.focus(), 0)
505
+ // 关闭&清理
506
+ const cleanup = () => {
507
+ window.removeEventListener('keydown', onKey)
508
+ window.removeEventListener('scroll', onScroll, true)
509
+ window.removeEventListener('resize', onScroll)
510
+ }
511
+ // ESC 关闭(仅最顶层)
512
+ const onKey = (e) => {
513
+ if (e.key !== 'Escape') return
514
+ // 只关闭当前最顶层的 AI 面板
515
+ const panels = Array.from(document.querySelectorAll('.ai-dialog'))
516
+ const topZ = panels.reduce((m, el) => Math.max(m, parseInt(getComputedStyle(el).zIndex || '0', 10) || 0), 0)
517
+ const myZ = parseInt(getComputedStyle(panel).zIndex || '0', 10) || 0
518
+ if (myZ === topZ) {
519
+ cleanup()
520
+ panel.remove()
521
+ }
522
+ }
523
+ window.addEventListener('keydown', onKey)
524
+
525
+ // 拖拽逻辑
526
+ let dragging = false
527
+ let offsetX = 0, offsetY = 0
528
+ const onMouseDown = (e) => {
529
+ dragging = true
530
+ panel.style.zIndex = String(++aiZ)
531
+ const rect = panel.getBoundingClientRect()
532
+ offsetX = e.clientX - rect.left
533
+ offsetY = e.clientY - rect.top
534
+ document.addEventListener('mousemove', onMouseMove)
535
+ document.addEventListener('mouseup', onMouseUp, { once: true })
536
+ }
537
+ const onMouseMove = (e) => {
538
+ if (!dragging) return
539
+ const nx = Math.min(Math.max(0, e.clientX - offsetX), window.innerWidth - panel.offsetWidth)
540
+ const ny = Math.min(Math.max(0, e.clientY - offsetY), window.innerHeight - panel.offsetHeight)
541
+ panel.style.left = nx + 'px'
542
+ panel.style.top = ny + 'px'
543
+ }
544
+ const onMouseUp = () => {
545
+ dragging = false
546
+ document.removeEventListener('mousemove', onMouseMove)
547
+ // 更新拖拽后的相对偏移:后续滚动/缩放按该偏移跟随
548
+ const ar = getAnchorRect()
549
+ const originX = ar ? (ar.right + pad) : (initialOriginX ?? 0)
550
+ const originY = ar ? (ar.bottom + pad) : (initialOriginY ?? 0)
551
+ const left = parseFloat(panel.style.left || '0')
552
+ const top = parseFloat(panel.style.top || '0')
553
+ deltaX = left - originX
554
+ deltaY = top - originY
555
+ }
556
+ head.addEventListener('mousedown', onMouseDown)
557
+
558
+ // 滚动/缩放时跟随组位置(通过快照实时计算)
559
+ const onScroll = () => {
560
+ if (dragging) return
561
+ const ar = getAnchorRect()
562
+ if (!ar) return
563
+ let px = ar.right + pad + deltaX
564
+ let py = ar.bottom + pad + deltaY
565
+ panel.style.left = px + 'px'
566
+ panel.style.top = py + 'px'
567
+ }
568
+ window.addEventListener('scroll', onScroll, true)
569
+ window.addEventListener('resize', onScroll)
570
+
571
+ // 关闭按钮清理
572
+ const origClose = closeBtn.onclick
573
+ closeBtn.onclick = () => { cleanup(); origClose && origClose(null) }
574
+ }
575
+
576
+
577
+ if (container) {
578
+ console.log({ container })
579
+ // 初始化:在容器内,允许框选所有图片 img,也可以改为其他选择器,如 '.card'
580
+ const ctrl = marqueeSelection({
581
+ container,
582
+ selectable: '*',
583
+ // 排除 InfiniteCanvas 外层容器与内部 content 包裹节点
584
+ exclude: ['#container', '.stage-content'],
585
+ toolbarButtons: [
586
+ {
587
+ label: 'AI',
588
+ title: 'AI 对话',
589
+ onClick: (ctx) => openAIDialog(ctx)
590
+ }
591
+ ],
592
+ quickGroupOnDblClick: true, // 双击快速创建一个新组
593
+ selectionMode: 'intersects', // 或 'center' | 'contains'
594
+ minOverlapRatio: 0.95, // 要求至少 80% 的元素面积被覆盖
595
+ overlapMetric: 'element',// 或 'iou'
596
+ conflictStrategy: 'none', // 父子冲突保留得分更高者
597
+ multi: true, // 开启多次圈选
598
+ combineMode: 'auto', // Shift=加选 Alt=减选 Ctrl/⌘=切换
599
+ groupMode: true, // 开启编组模式
600
+ hoverHighlight: true,
601
+ hoverClass: 'hovered',
602
+ allowIntersectionSelection: false, // 允许框选与元素相交即选中
603
+ allowUnionSelection: true, // 允许框选包含元素的并集
604
+ allowContainmentSelection:true,
605
+ // 组主题颜色:启用随机配色与调色板(演示)
606
+ groupRandomColor: true,
607
+ // groupColorPalette: ['#f97316', '#eab308', '#22c55e', '#06b6d4', '#3b82f6', '#8b5cf6', '#ec4899'],
608
+ onChange: (payload) => {
609
+ // if (payload.type === 'groups') {
610
+ // console.log('组变更', payload.groups.map(g => g.length), '合并选中', payload.flat.length)
611
+ // } else {
612
+ // console.log('单次选中', payload.selected.length)
613
+ // }
614
+ },
615
+ onSelectionEnd: (snapshot) => {
616
+ console.log({ snapshot })
617
+ lastSnapshot = snapshot
618
+ const panel = document.getElementById('selection-panel')
619
+ const content = document.getElementById('selection-panel-content')
620
+ const btn = document.getElementById('btn-toggle-nested')
621
+ if (!panel || !content || !btn) return
622
+ panel.style.display = 'block'
623
+
624
+ const labelOf = (el) => {
625
+ // 暴露 ctrl 为全局,供 InfiniteCanvas 的回调与调试使用
626
+ // @ts-ignore
627
+ window.ctrl = ctrl
628
+ if (!el || !el.tagName) return String(el)
629
+ const id = el.id ? `#${el.id}` : ''
630
+ const text = (el.firstChild?.textContent || '').trim().slice(0, 20)
631
+ return `<span class=\"node-label\">${el.tagName.toLowerCase()}${id}${text ? `<span class=\"node-content\">${text}</span>` : ''}</span>`
632
+ }
633
+ // 将 els 构造成仅包含直系选中后代的树(过滤中间层)
634
+ const buildTree = (els) => {
635
+ const roots = els.filter(el => !els.some(other => other !== el && other.contains(el)))
636
+ const buildNode = (el) => {
637
+ const children = els.filter(ch => ch !== el && el.contains(ch) && !els.some(o => o !== ch && o !== el && o.contains(ch) && el.contains(o)))
638
+ return { el, children: children.map(buildNode) }
639
+ }
640
+ return roots.map(buildNode)
641
+ }
642
+ const renderTree = (nodes) => {
643
+ const renderUl = (ns) => `<ul>${ns.map(n => `<li>${labelOf(n.el)}${n.children?.length ? renderUl(n.children) : ''}</li>`).join('')}</ul>`
644
+ return `<div class=\"tree\">${renderUl(nodes)}</div>`
645
+ }
646
+
647
+ const renderFlat = (snap) => {
648
+ if (snap.type === 'groups') {
649
+ const head = [`组数:${snap.groups.length}`, `并集元素:${snap.flat.length}`].map(l => `<div class=\"group-row\">${l}</div>`).join('')
650
+ const groupsHtml = snap.groups.map((g, i) => {
651
+ const rect = snap.groupRects[i]
652
+ const vis = snap.hidden[i] ? '隐藏' : '显示'
653
+ const title = `第${i + 1}组:元素${g.length}个,${vis}${rect ? `,框(${Math.round(rect.left)},${Math.round(rect.top)} ${Math.round(rect.width)}×${Math.round(rect.height)})` : ''}`
654
+ const tree = renderTree(buildTree(g))
655
+ return `<details class=\"group\"><summary>${title}</summary>${tree}</details>`
656
+ }).join('')
657
+ content.innerHTML = head + groupsHtml
658
+ } else {
659
+ content.innerHTML = `<div>单次选中:${snap.selected.length}</div>`
660
+ }
661
+ }
662
+
663
+ // 组级嵌套:基于 snapshot.groupNesting(库层提供)
664
+ const renderNested = (snap) => {
665
+ if (snap.type !== 'groups') {
666
+ const tree = renderTree(buildTree(snap.selected))
667
+ content.innerHTML = `<details class=\"group\" open><summary>单次选中嵌套</summary>${tree}</details>`
668
+ return
669
+ }
670
+ const { parents, children, roots } = snap.groupNesting
671
+ const titleOf = (i) => {
672
+ const g = snap.groups[i]
673
+ const rect = snap.groupRects[i]
674
+ const vis = snap.hidden[i] ? '隐藏' : '显示'
675
+ return `第${i + 1}组:元素${g.length}个,${vis}${rect ? `,框(${Math.round(rect.left)},${Math.round(rect.top)} ${Math.round(rect.width)}×${Math.round(rect.height)})` : ''}`
676
+ }
677
+ const renderGroupNode = (i) => {
678
+ const tree = renderTree(buildTree(snap.groups[i]))
679
+ const kids = (children[i] || [])?.map(renderGroupNode).join('') || ''
680
+ return `<details class=\"group\"><summary>${titleOf(i)}</summary>${tree}${kids}</details>`
681
+ }
682
+ const head = [`组嵌套查看`, `组数:${snap.groups.length}`].map(l => `<div class=\"group-row\">${l}</div>`).join('')
683
+ content.innerHTML = head + roots.map(renderGroupNode).join('')
684
+ }
685
+
686
+ const renderPanel = () => nestedMode ? renderNested(lastSnapshot) : renderFlat(lastSnapshot)
687
+ btn.textContent = nestedMode ? '平铺查看' : '组嵌套查看'
688
+ btn.onclick = (ev) => {
689
+ ev.stopPropagation()
690
+ nestedMode = !nestedMode
691
+ btn.textContent = nestedMode ? '平铺查看' : '组嵌套查看'
692
+ renderPanel()
693
+ }
694
+ renderPanel()
695
+ }
696
+ })
697
+ // 暴露 ctrl 为全局,方便调试或外部调用
698
+ // @ts-ignore
699
+ window.ctrl = ctrl
700
+
701
+ // 绑定:根据选择器成组
702
+ const selectorInput = document.getElementById('group-selector-input')
703
+ const addGroupBtn = document.getElementById('btn-add-group-by-selector')
704
+ const handleAddGroup = () => {
705
+ if (!selectorInput) return
706
+ const qs = selectorInput.value.trim()
707
+ if (!qs) {
708
+ selectorInput.focus()
709
+ return
710
+ }
711
+ let list = []
712
+ try {
713
+ list = Array.from(container.querySelectorAll(qs))
714
+ } catch (err) {
715
+ console.warn('无效的 CSS 选择器:', qs, err)
716
+ alert('无效的 CSS 选择器,请检查输入。')
717
+ return
718
+ }
719
+ if (!list.length) {
720
+ alert('未匹配到任何元素。')
721
+ return
722
+ }
723
+ const ok = ctrl && typeof ctrl.addGroup === 'function' ? ctrl.addGroup(list) : false
724
+ if (!ok) {
725
+ // 失败原因可能:重复编组、交/并限制冲突、元素不在容器/被排除/不匹配 selectable
726
+ alert('成组失败:可能与当前规则冲突或与已有组重复。')
727
+ }
728
+ }
729
+ addGroupBtn?.addEventListener('click', handleAddGroup)
730
+ selectorInput?.addEventListener('keydown', (e) => {
731
+ if (e.key === 'Enter') handleAddGroup()
732
+ })
733
+ } else {
734
+ console.error('container not found')
735
+ }
736
+
737
+ </script>
738
+ </body>
739
+
740
+ </html>