@painekillers/tree-builder-viewer 0.0.0-stage → 1.0.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.
@@ -0,0 +1,488 @@
1
+ :host {
2
+ display: block;
3
+ position: relative;
4
+
5
+ width: 100%;
6
+ height: 100%;
7
+ }
8
+
9
+ canvas {
10
+ display: block;
11
+
12
+ width: 100%;
13
+ height: 100%;
14
+ }
15
+
16
+ #ui {
17
+ position: absolute;
18
+ inset: 0;
19
+
20
+ width: 100%;
21
+ height: 100%;
22
+
23
+ pointer-events: none;
24
+ }
25
+
26
+ #ui > * {
27
+ pointer-events: auto;
28
+ }
29
+
30
+ #search-button {
31
+ position: absolute;
32
+ left: 20px;
33
+ bottom: 20px;
34
+
35
+ width: 44px;
36
+ height: 44px;
37
+
38
+ padding: 10px;
39
+
40
+ border: 1px solid rgba(0, 0, 0, 0.08);
41
+ border-radius: 10px;
42
+
43
+ background: #f8f8f8;
44
+
45
+ box-shadow:
46
+ 0 2px 4px rgba(0, 0, 0, 0.14);
47
+
48
+ cursor: pointer;
49
+
50
+ transition:
51
+ transform 0.18s ease,
52
+ box-shadow 0.18s ease;
53
+ }
54
+
55
+ #search-button:hover {
56
+ transform: scale(1.04);
57
+
58
+ box-shadow:
59
+ 0 4px 16px rgba(0, 0, 0, 0.14);
60
+ }
61
+
62
+ #search-button svg {
63
+ width: 100%;
64
+ height: 100%;
65
+
66
+ fill: none;
67
+ stroke: currentColor;
68
+ stroke-width: 2;
69
+ stroke-linecap: round;
70
+ }
71
+
72
+ #search-menu {
73
+ position: absolute;
74
+ left: 50%;
75
+ top: 50%;
76
+
77
+ width: min(700px, calc(100% - 32px));
78
+ height: min(600px, calc(100% - 32px));
79
+ padding: 16px;
80
+
81
+ transform: translate(-50%, -50%);
82
+
83
+ border: 1px solid rgba(0, 0, 0, 0.08);
84
+ border-radius: 14px;
85
+
86
+ background: #f8f8f8;
87
+
88
+ box-shadow:
89
+ 0 8px 32px rgba(0, 0, 0, 0.14);
90
+
91
+ display: none;
92
+ box-sizing: border-box;
93
+ }
94
+
95
+ .menu-header {
96
+ display: flex;
97
+ align-items: center;
98
+ justify-content: space-between;
99
+
100
+ height: 32px;
101
+ }
102
+
103
+ .menu-header span {
104
+ font-size: 16px;
105
+ font-weight: 500;
106
+ }
107
+
108
+ .menu-header button {
109
+ width: 32px;
110
+ height: 32px;
111
+
112
+ padding: 0;
113
+
114
+ border: none;
115
+ border-radius: 8px;
116
+
117
+ background: transparent;
118
+ color: #333;
119
+
120
+ font-size: 22px;
121
+
122
+ cursor: pointer;
123
+ }
124
+
125
+ .menu-header button:hover {
126
+ background: rgba(0, 0, 0, 0.06);
127
+ }
128
+
129
+ #search-input {
130
+ width: 100%;
131
+ box-sizing: border-box;
132
+
133
+ margin-top: 12px;
134
+
135
+ padding: 12px 14px;
136
+
137
+ border: 1px solid rgba(0, 0, 0, 0.08);
138
+ border-radius: 10px;
139
+
140
+ outline: none;
141
+
142
+ background: white;
143
+
144
+ font-size: 16px;
145
+ }
146
+
147
+ #search-results {
148
+ height: calc(100% - 96px);
149
+ margin-top: 12px;
150
+
151
+ overflow-y: auto;
152
+ }
153
+
154
+ .search-entry {
155
+ display: flex;
156
+ align-items: center;
157
+ justify-content: space-between;
158
+
159
+ padding: 12px 14px;
160
+ margin-bottom: 8px;
161
+
162
+ border: 1px solid rgba(0, 0, 0, 0.08);
163
+ border-radius: 10px;
164
+
165
+ background: white;
166
+
167
+ box-shadow:
168
+ 0 2px 8px rgba(0, 0, 0, 0.08);
169
+
170
+ transition:
171
+ transform 0.18s ease,
172
+ box-shadow 0.18s ease;
173
+ }
174
+
175
+ .search-entry:hover {
176
+ transform: scale(1.01);
177
+
178
+ box-shadow:
179
+ 0 4px 16px rgba(0, 0, 0, 0.12);
180
+ }
181
+
182
+ .search-entry-name {
183
+ font-size: 15px;
184
+ }
185
+
186
+ .search-entry-actions {
187
+ display: flex;
188
+ gap: 6px;
189
+ }
190
+
191
+ .search-entry-alias {
192
+ margin-top: 2px;
193
+
194
+ color: #888;
195
+ font-size: 13px;
196
+ }
197
+
198
+ .search-entry-actions button {
199
+ display: flex;
200
+ align-items: center;
201
+ justify-content: center;
202
+
203
+ width: 34px;
204
+ height: 34px;
205
+
206
+ padding: 7px;
207
+
208
+ border: 1px solid rgba(0, 0, 0, 0.08);
209
+ border-radius: 8px;
210
+
211
+ background: #f8f8f8;
212
+
213
+ box-shadow:
214
+ 0 2px 6px rgba(0, 0, 0, 0.08);
215
+
216
+ cursor: pointer;
217
+
218
+ transition:
219
+ transform 0.12s,
220
+ box-shadow 0.12s;
221
+ }
222
+
223
+ .search-entry-actions button svg {
224
+ width: 100%;
225
+ height: 100%;
226
+
227
+ fill: none;
228
+
229
+ stroke: currentColor;
230
+ stroke-width: 2;
231
+
232
+ stroke-linecap: round;
233
+ stroke-linejoin: round;
234
+ }
235
+
236
+ .search-entry-actions button:hover:not(:disabled) {
237
+ transform: scale(1.05);
238
+
239
+ box-shadow:
240
+ 0 4px 12px rgba(0, 0, 0, 0.14);
241
+ }
242
+
243
+ .search-entry-actions button:disabled {
244
+ opacity: 0.5;
245
+ cursor: default;
246
+ }
247
+
248
+ #theme-button {
249
+ position: absolute;
250
+ left: 76px;
251
+ bottom: 20px;
252
+
253
+ width: 44px;
254
+ height: 44px;
255
+
256
+ padding: 10px;
257
+
258
+ border: 1px solid rgba(0, 0, 0, 0.08);
259
+ border-radius: 10px;
260
+
261
+ background: #f8f8f8;
262
+
263
+ box-shadow:
264
+ 0 2px 4px rgba(0, 0, 0, 0.14);
265
+
266
+ cursor: pointer;
267
+
268
+ transition:
269
+ transform 0.18s ease,
270
+ box-shadow 0.18s ease;
271
+ }
272
+
273
+ #theme-button:hover {
274
+ transform: scale(1.04);
275
+
276
+ box-shadow:
277
+ 0 4px 16px rgba(0, 0, 0, 0.14);
278
+ }
279
+
280
+ #theme-button svg {
281
+ width: 100%;
282
+ height: 100%;
283
+
284
+ fill: none;
285
+ stroke: currentColor;
286
+ stroke-width: 2;
287
+ stroke-linecap: round;
288
+ }
289
+
290
+ #theme-menu {
291
+ position: absolute;
292
+ left: 50%;
293
+ top: 50%;
294
+
295
+ width: min(650px, calc(100% - 32px));
296
+ height: min(520px, calc(100% - 32px));
297
+ padding: 16px;
298
+
299
+ transform: translate(-50%, -50%);
300
+
301
+ border: 1px solid rgba(0, 0, 0, 0.08);
302
+ border-radius: 14px;
303
+
304
+ background: #f8f8f8;
305
+
306
+ box-shadow:
307
+ 0 8px 32px rgba(0, 0, 0, 0.14);
308
+
309
+ display: none;
310
+ box-sizing: border-box;
311
+
312
+ overflow-y: auto;
313
+ }
314
+
315
+ #theme-grid {
316
+ display: grid;
317
+ grid-template-columns: repeat(auto-fit, 150px);
318
+ gap: 16px;
319
+
320
+ width: 100%;
321
+ padding: 4px;
322
+
323
+ box-sizing: border-box;
324
+
325
+ margin-top: 12px;
326
+ }
327
+
328
+ .theme-entry {
329
+ width: 150px;
330
+ height: 130px;
331
+
332
+ padding: 0;
333
+
334
+ border: 1px solid rgba(0, 0, 0, 0.12);
335
+ border-radius: 12px;
336
+
337
+ background: transparent;
338
+
339
+ overflow: hidden;
340
+
341
+ cursor: pointer;
342
+
343
+ box-sizing: border-box;
344
+
345
+ display: flex;
346
+ flex-direction: column;
347
+
348
+ transition:
349
+ transform 0.12s,
350
+ box-shadow 0.12s,
351
+ border-color 0.12s;
352
+ }
353
+
354
+ .theme-entry:hover {
355
+ transform: scale(1.02);
356
+
357
+ border-color: rgba(0, 0, 0, 0.2);
358
+
359
+ box-shadow:
360
+ 0 4px 12px rgba(0, 0, 0, 0.12);
361
+ }
362
+
363
+ .theme-entry.selected {
364
+ border-color: #707070;
365
+
366
+ box-shadow:
367
+ 0 0 0 2px rgba(0, 0, 0, 0.12);
368
+ }
369
+
370
+ .theme-preview {
371
+ width: 100%;
372
+ height: 90px;
373
+
374
+ flex-shrink: 0;
375
+
376
+ margin: 0;
377
+
378
+ border: none;
379
+
380
+ background: transparent;
381
+ }
382
+
383
+ .theme-name {
384
+ width: 100%;
385
+ height: 40px;
386
+
387
+ flex-shrink: 0;
388
+
389
+ padding: 0 14px;
390
+
391
+ display: flex;
392
+ align-items: center;
393
+ justify-content: flex-start;
394
+
395
+ box-sizing: border-box;
396
+
397
+ background: white;
398
+
399
+ color: #333;
400
+
401
+ font-size: 14px;
402
+ font-weight: 500;
403
+ letter-spacing: 0.2px;
404
+ }
405
+
406
+ #node-menu {
407
+ position: absolute;
408
+ left: 50%;
409
+ top: 50%;
410
+
411
+ width: min(700px, calc(100% - 32px));
412
+ height: min(600px, calc(100% - 32px));
413
+ padding: 16px;
414
+
415
+ transform: translate(-50%, -50%);
416
+
417
+ border: 1px solid rgba(0, 0, 0, 0.08);
418
+ border-radius: 14px;
419
+
420
+ background: #f8f8f8;
421
+
422
+ box-shadow:
423
+ 0 8px 32px rgba(0, 0, 0, 0.14);
424
+
425
+ display: none;
426
+ box-sizing: border-box;
427
+ }
428
+
429
+ #node-menu-content {
430
+ height: calc(100% - 44px);
431
+ margin-top: 12px;
432
+
433
+ overflow-y: auto;
434
+ }
435
+
436
+ #tooltip {
437
+ position: absolute;
438
+
439
+ padding: 7px 11px;
440
+
441
+ border: 1px solid rgba(0, 0, 0, 0.08);
442
+ border-radius: 9px;
443
+
444
+ background: #f8f8f8;
445
+ color: #333;
446
+
447
+ font-size: 13px;
448
+
449
+ box-shadow:
450
+ 0 4px 14px rgba(0, 0, 0, 0.12);
451
+
452
+ pointer-events: none;
453
+
454
+ opacity: 0;
455
+
456
+ transform: translate(-50%, -100%);
457
+
458
+ transition:
459
+ opacity 0.12s,
460
+ transform 0.12s;
461
+
462
+ z-index: 1000;
463
+ }
464
+
465
+ #tooltip::after {
466
+ content: "";
467
+
468
+ position: absolute;
469
+
470
+ left: 50%;
471
+ bottom: -5px;
472
+
473
+ width: 9px;
474
+ height: 9px;
475
+
476
+ border-right: 1px solid rgba(0, 0, 0, 0.08);
477
+ border-bottom: 1px solid rgba(0, 0, 0, 0.08);
478
+
479
+ background: #f8f8f8;
480
+
481
+ transform:
482
+ translateX(-50%)
483
+ rotate(45deg);
484
+ }
485
+
486
+ #tooltip.visible {
487
+ opacity: 1;
488
+ }
package/tooltip.js ADDED
@@ -0,0 +1,78 @@
1
+ export default class Tooltip {
2
+ #target = null
3
+ #duration
4
+
5
+ constructor(
6
+ container,
7
+ {
8
+ offset = 8,
9
+ duration = 120
10
+ } = {}
11
+ ) {
12
+ this.offset = offset
13
+ this.#duration = duration
14
+
15
+ this.element = document.createElement("div")
16
+ this.element.id = "tooltip"
17
+
18
+ this.element.style.transitionDuration =
19
+ `${duration}ms`
20
+
21
+ document.body.append(this.element)
22
+
23
+ container.addEventListener("mouseover", e => {
24
+ const target = e.target.closest("[data-tooltip]")
25
+
26
+ if(!target) return
27
+
28
+ this.show(target.dataset.tooltip, target)
29
+ })
30
+
31
+ container.addEventListener("mouseout", e => {
32
+ const target = e.target.closest("[data-tooltip]")
33
+
34
+ if(target !== this.#target) return
35
+
36
+ this.hide()
37
+ })
38
+
39
+ new MutationObserver(() => {
40
+ if(
41
+ this.#target &&
42
+ !container.contains(this.#target)
43
+ ) {
44
+ this.hide()
45
+ }
46
+ }).observe(container, {
47
+ childList: true,
48
+ subtree: true
49
+ })
50
+ }
51
+
52
+ set duration(value) {
53
+ this.#duration = value
54
+ this.element.style.transitionDuration =
55
+ `${value}ms`
56
+ }
57
+
58
+ show(text, target) {
59
+ this.#target = target
60
+
61
+ this.element.textContent = text
62
+
63
+ const rect = target.getBoundingClientRect()
64
+
65
+ this.element.style.left =
66
+ rect.left + rect.width / 2 + "px"
67
+
68
+ this.element.style.top =
69
+ rect.top - this.offset + "px"
70
+
71
+ this.element.classList.add("visible")
72
+ }
73
+
74
+ hide() {
75
+ this.#target = null
76
+ this.element.classList.remove("visible")
77
+ }
78
+ }