@comty.io/core 0.1.0 → 0.1.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/README.md +7 -1
- package/comty-sdk.js +1046 -210
- package/package.json +2 -2
- package/src/browser.js +531 -99
- package/src/capture.js +104 -1
- package/src/dev.mjs +3 -2
- package/src/measurements.js +209 -0
- package/src/style.js +203 -111
- package/src/vite.mjs +3 -2
package/comty-sdk.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
// Generated by npm run build in sdk/. Edit src/browser.js and its imported modules instead.
|
|
2
2
|
(function () {
|
|
3
3
|
function getComtyStyle() {
|
|
4
|
+
const toolbarSurface = "#2b2d2f";
|
|
5
|
+
const toolbarMuted = "#b8babb";
|
|
4
6
|
const commentCursor = `<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40"><path d="M20 2C30 2 38 10 38 20S30 38 20 38H3V28C2 25 2 23 2 20 2 10 10 2 20 2Z" fill="white" stroke="#d6dbe2"/><circle cx="20" cy="20" r="14" fill="#e6e6e6"/><path d="M20 14v12m-6-6h12" fill="none" stroke="#404040" stroke-width="2.5" stroke-linecap="round"/></svg>`;
|
|
7
|
+
const typographyCursor = `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><rect x="8" y="8" width="23" height="23" rx="7" fill="${toolbarSurface}" stroke="${toolbarMuted}" stroke-width=".75"/><path d="m14 25 5.5-12L25 25m-8.5-4h6M13 25h4m5 0h4" fill="none" stroke="${toolbarMuted}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="m2 2 2 15 4-5 6-3Z" fill="${toolbarSurface}" stroke="${toolbarMuted}" stroke-width="1.5" stroke-linejoin="round"/></svg>`;
|
|
5
8
|
return `
|
|
6
9
|
#comty-root {
|
|
7
|
-
--comty-surface:
|
|
8
|
-
--comty-hover-surface:
|
|
10
|
+
--comty-surface: var(--comty-toolbar-surface);
|
|
11
|
+
--comty-hover-surface: var(--comty-toolbar-selected);
|
|
9
12
|
--comty-foreground: #fafafa;
|
|
10
|
-
--comty-muted:
|
|
13
|
+
--comty-muted: var(--comty-toolbar-muted);
|
|
11
14
|
--comty-accent: #55aaf5;
|
|
12
15
|
--comty-agent-warning: #e9b86d;
|
|
13
16
|
--comty-agent-success: #80c9a4;
|
|
@@ -15,8 +18,8 @@ function getComtyStyle() {
|
|
|
15
18
|
--comty-outline: oklch(0.62 0.18 255);
|
|
16
19
|
--comty-paper: #fff;
|
|
17
20
|
--comty-ink: #171717;
|
|
18
|
-
--comty-border: #
|
|
19
|
-
--comty-field-surface:
|
|
21
|
+
--comty-border: #434649;
|
|
22
|
+
--comty-field-surface: var(--comty-toolbar-group);
|
|
20
23
|
--comty-xray: #0000ff;
|
|
21
24
|
--comty-guide: #ff0000;
|
|
22
25
|
--comty-margin-fill: rgb(246 172 88 / .45);
|
|
@@ -25,15 +28,48 @@ function getComtyStyle() {
|
|
|
25
28
|
--comty-floating-shadow: 0 .25rem 1rem rgb(0 0 0 / .16);
|
|
26
29
|
--comty-tool-size: 2.375rem;
|
|
27
30
|
--comty-toolbar-padding: .375rem;
|
|
31
|
+
--comty-toolbar-gap: .375rem;
|
|
32
|
+
--comty-toolbar-grip-width: .6rem;
|
|
33
|
+
--comty-toolbar-grip-gap: .25rem;
|
|
34
|
+
--comty-magnifier-size: 5.375rem;
|
|
35
|
+
--comty-magnifier-rim: .625rem;
|
|
36
|
+
--comty-magnifier-line: .125rem;
|
|
37
|
+
--comty-magnifier-grid-line: .03125rem;
|
|
38
|
+
--comty-magnifier-dark: #111;
|
|
39
|
+
--comty-magnifier-gray: #999;
|
|
40
|
+
--comty-magnifier-shadow: 0 .0625rem .375rem rgb(0 0 0 / .5);
|
|
41
|
+
--comty-property-width: 16rem;
|
|
42
|
+
--comty-property-swatch: 2rem;
|
|
43
|
+
--comty-property-font-size: .6875rem;
|
|
44
|
+
--comty-property-row-height: 1.375rem;
|
|
45
|
+
--comty-toolbar-surface: ${toolbarSurface};
|
|
46
|
+
--comty-toolbar-group: #202224;
|
|
47
|
+
--comty-toolbar-selected: #505457;
|
|
48
|
+
--comty-toolbar-muted: ${toolbarMuted};
|
|
49
|
+
--comty-measure: #ff3b30;
|
|
50
|
+
--comty-font: "Inter", ui-sans-serif, system-ui, sans-serif;
|
|
51
|
+
--comty-dialog-surface: var(--comty-surface);
|
|
52
|
+
--comty-dialog-ink: var(--comty-foreground);
|
|
53
|
+
--comty-dialog-soft: var(--comty-muted);
|
|
54
|
+
--comty-dialog-field: var(--comty-field-surface);
|
|
55
|
+
--comty-dialog-line: var(--comty-border);
|
|
56
|
+
--comty-dialog-backdrop: rgb(0 0 0 / .4);
|
|
57
|
+
--comty-dialog-radius: 2rem;
|
|
58
|
+
--comty-dialog-width: 32rem;
|
|
59
|
+
--comty-dialog-shadow: 0 1.5rem 4rem rgb(0 0 0 / .16);
|
|
28
60
|
--comty-motion-duration: 220ms;
|
|
61
|
+
--comty-working-dot-size: .25em;
|
|
62
|
+
--comty-working-dot-gap: .125em;
|
|
29
63
|
--comty-ease-out: cubic-bezier(0.23, 1, 0.32, 1);
|
|
64
|
+
--comty-ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
|
|
30
65
|
position: fixed;
|
|
31
66
|
inset: 0;
|
|
32
67
|
width: 100%; height: 100%; max-width: none; max-height: none;
|
|
33
68
|
margin: 0; padding: 0; border: 0; overflow: visible; background: transparent;
|
|
34
69
|
z-index: 2147483647;
|
|
35
70
|
pointer-events: none;
|
|
36
|
-
font-family:
|
|
71
|
+
font-family: var(--comty-font);
|
|
72
|
+
color-scheme: dark;
|
|
37
73
|
}
|
|
38
74
|
html.comty-agent-enabled body { transition: margin-left 220ms cubic-bezier(0.23, 1, 0.32, 1), margin-right 220ms cubic-bezier(0.23, 1, 0.32, 1); }
|
|
39
75
|
html.comty-agent-docked[data-comty-agent-side="left"] body { margin-left: 22rem; }
|
|
@@ -59,7 +95,9 @@ function getComtyStyle() {
|
|
|
59
95
|
.comty-agent-row { position: relative; display: flex; align-items: center; gap: .625rem; min-height: 2rem; padding: .25rem .5rem; border-radius: .5rem; color: var(--comty-foreground); }
|
|
60
96
|
.comty-agent-row:hover { background: var(--comty-hover-surface); }
|
|
61
97
|
.comty-agent-dot { width: .4375rem; height: .4375rem; flex: 0 0 auto; border-radius: 50%; background: var(--comty-muted); }
|
|
62
|
-
.comty-
|
|
98
|
+
.comty-working-indicator { display: inline-grid; grid-template-columns: repeat(3, var(--comty-working-dot-size)); gap: var(--comty-working-dot-gap); flex: 0 0 auto; color: var(--comty-foreground); }
|
|
99
|
+
.comty-working-indicator > i { display: block; width: var(--comty-working-dot-size); height: var(--comty-working-dot-size); border-radius: 50%; background: currentColor; opacity: .25; transition: opacity 150ms linear; }
|
|
100
|
+
.comty-working-indicator > i[data-active] { opacity: 1; }
|
|
63
101
|
.comty-agent-dot[data-status="success"] { background: var(--comty-agent-success); }
|
|
64
102
|
.comty-agent-dot[data-status="error"] { background: var(--comty-agent-error); }
|
|
65
103
|
.comty-agent-row-name { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; }
|
|
@@ -69,7 +107,7 @@ function getComtyStyle() {
|
|
|
69
107
|
@media (hover: none) { .comty-agent-row-actions { position: static; opacity: 1; pointer-events: auto; transform: none; background: transparent; } }
|
|
70
108
|
.comty-agent-panel .comty-agent-row-actions .comty-agent-action { min-height: 1.5rem; padding: .125rem .375rem; border-radius: .25rem; background: transparent; color: var(--comty-muted); font-size: .875rem; }
|
|
71
109
|
.comty-agent-panel .comty-agent-row-actions .comty-agent-action:hover { color: var(--comty-foreground); background: var(--comty-surface); }
|
|
72
|
-
.comty-agent-error-popover { position: fixed; z-index: 2147483647; width: min(20rem, calc(100vw - 1rem)); max-height: calc(100vh - 1rem); overflow: auto; padding: .75rem .875rem; border:
|
|
110
|
+
.comty-agent-error-popover { position: fixed; z-index: 2147483647; width: min(20rem, calc(100vw - 1rem)); max-height: calc(100vh - 1rem); overflow: auto; padding: .75rem .875rem; border: 0; border-radius: .625rem; background: var(--comty-surface); box-shadow: var(--comty-floating-shadow); color: var(--comty-foreground); font: .875rem/1.4 ui-sans-serif, system-ui, sans-serif; pointer-events: none; color-scheme: dark; }
|
|
73
111
|
.comty-agent-error-popover strong { display: block; font-weight: 600; }
|
|
74
112
|
.comty-agent-error-popover p { margin: .375rem 0 0; color: var(--comty-muted); white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
75
113
|
.comty-agent-panel p { margin: .5rem 0 0; font: inherit; color: var(--comty-muted); }
|
|
@@ -93,10 +131,8 @@ function getComtyStyle() {
|
|
|
93
131
|
.comty-agent-empty strong { font-size: .875rem; font-weight: 500; }
|
|
94
132
|
.comty-agent-empty p { max-width: 15rem; margin: .5rem auto 0; font-size: .875rem; }
|
|
95
133
|
.comty-agent-working { position: fixed; z-index: 1; box-sizing: border-box; border: 2px solid var(--comty-accent); border-radius: .25rem; pointer-events: none; }
|
|
96
|
-
.comty-agent-working
|
|
97
|
-
.comty-agent-working[data-badge-below]
|
|
98
|
-
.comty-agent-working span::before { content: ""; display: inline-block; width: .4rem; height: .4rem; margin-right: .4rem; border-radius: 50%; background: currentColor; animation: comty-agent-pulse 1.4s ease-in-out infinite; }
|
|
99
|
-
@keyframes comty-agent-pulse { 50% { opacity: .3; } }
|
|
134
|
+
.comty-agent-working-badge { position: absolute; left: -.125rem; bottom: 100%; display: inline-flex; align-items: center; gap: .4rem; padding: .25rem .5rem; border-radius: .375rem .375rem 0 0; background: var(--comty-surface); color: var(--comty-foreground); font: 600 .6875rem/1.4 ui-sans-serif, system-ui, sans-serif; white-space: nowrap; }
|
|
135
|
+
.comty-agent-working[data-badge-below] .comty-agent-working-badge { bottom: auto; top: 100%; border-radius: 0 0 .375rem .375rem; }
|
|
100
136
|
#comty-root { cursor: default; }
|
|
101
137
|
#comty-root::backdrop { background: transparent; pointer-events: none; }
|
|
102
138
|
html.comty-comment-placement,
|
|
@@ -114,22 +150,24 @@ function getComtyStyle() {
|
|
|
114
150
|
z-index: 2147483647;
|
|
115
151
|
display: flex;
|
|
116
152
|
align-items: center;
|
|
117
|
-
gap:
|
|
153
|
+
gap: var(--comty-toolbar-gap);
|
|
118
154
|
max-width: calc(100vw - 2rem);
|
|
119
155
|
padding: var(--comty-toolbar-padding);
|
|
120
|
-
border-radius: .
|
|
121
|
-
color: var(--comty-
|
|
156
|
+
border-radius: 1.125rem;
|
|
157
|
+
color: var(--comty-toolbar-muted);
|
|
122
158
|
opacity: 0;
|
|
123
159
|
visibility: hidden;
|
|
124
160
|
pointer-events: none;
|
|
125
161
|
touch-action: none;
|
|
126
162
|
user-select: none;
|
|
127
|
-
cursor:
|
|
163
|
+
cursor: default;
|
|
128
164
|
isolation: isolate;
|
|
129
165
|
transition: opacity 180ms ease, visibility 0s linear 180ms;
|
|
130
166
|
}
|
|
131
167
|
#comty-root.comty-active .comty-toolbar { opacity: 1; visibility: visible; pointer-events: auto; transition-delay: 0s; }
|
|
132
|
-
.comty-toolbar-surface { position: absolute; inset: 0; z-index: -1; border-radius: inherit; background: var(--comty-surface); box-shadow: var(--comty-floating-shadow); transform-origin: top left; pointer-events: none; }
|
|
168
|
+
.comty-toolbar-surface { position: absolute; inset: 0; z-index: -1; border-radius: inherit; background: var(--comty-toolbar-surface); box-shadow: var(--comty-floating-shadow); transform-origin: top left; pointer-events: none; }
|
|
169
|
+
.comty-toolbar-grip { display: flex; align-items: center; justify-content: center; flex: 0 0 auto; width: var(--comty-toolbar-grip-width); height: var(--comty-tool-size); margin-inline-end: calc(var(--comty-toolbar-grip-gap) - var(--comty-toolbar-gap)); color: inherit; cursor: grab; }
|
|
170
|
+
.comty-toolbar-grip svg { width: 1.125rem; height: 1.125rem; flex-shrink: 0; }
|
|
133
171
|
.comty-toolbar[data-dragging], .comty-toolbar[data-dragging] * { cursor: grabbing; }
|
|
134
172
|
.comty-toolbar:focus-visible { outline: .125rem solid var(--comty-accent); outline-offset: .125rem; }
|
|
135
173
|
.comty-control-button {
|
|
@@ -153,13 +191,14 @@ function getComtyStyle() {
|
|
|
153
191
|
}
|
|
154
192
|
.comty-control-button svg { width: 1.125rem; height: 1.125rem; }
|
|
155
193
|
.comty-control-button:hover { background: var(--comty-hover-surface); }
|
|
156
|
-
.comty-control-button[aria-pressed="true"] { background: var(--comty-
|
|
194
|
+
.comty-control-button[aria-pressed="true"] { background: var(--comty-toolbar-selected); color: var(--comty-foreground); }
|
|
157
195
|
.comty-control-button:focus-visible, .comty-filter-dot:focus-visible {
|
|
158
|
-
outline: .125rem solid var(--comty-
|
|
196
|
+
outline: .125rem solid var(--comty-foreground);
|
|
159
197
|
outline-offset: .125rem;
|
|
160
198
|
}
|
|
161
|
-
.comty-tool-context { display: flex; align-items: center; gap: .125rem; }
|
|
162
|
-
.comty-tool-
|
|
199
|
+
.comty-tool-primary, .comty-tool-context { display: flex; align-items: center; gap: .125rem; padding: .125rem; border-radius: .875rem; background: var(--comty-toolbar-group); }
|
|
200
|
+
.comty-tool-primary { position: relative; z-index: 1; }
|
|
201
|
+
.comty-tool-divider { display: none; }
|
|
163
202
|
.comty-filter-controls { display: flex; align-items: center; gap: .375rem; padding-inline: .5rem; }
|
|
164
203
|
.comty-filter-dot { flex-shrink: 0; width: .625rem; height: .625rem; padding: 0; border: 0; border-radius: 50%; cursor: pointer; }
|
|
165
204
|
.comty-filter-dot-muted { opacity: .35; }
|
|
@@ -167,7 +206,7 @@ function getComtyStyle() {
|
|
|
167
206
|
[data-comty-anchor-hidden] { visibility: hidden !important; pointer-events: none !important; }
|
|
168
207
|
@media (prefers-reduced-motion: reduce) {
|
|
169
208
|
html.comty-agent-enabled body { transition: none; }
|
|
170
|
-
.comty-toolbar, .comty-control-button, .comty-sidepanel, .comty-pin, .comty-bubble, .comty-agent-panel, .comty-agent-action, .comty-agent-dot, .comty-
|
|
209
|
+
.comty-toolbar, .comty-control-button, .comty-sidepanel, .comty-pin, .comty-bubble, .comty-agent-panel, .comty-agent-action, .comty-agent-dot, .comty-working-indicator > i { transition: none !important; animation: none !important; }
|
|
171
210
|
}
|
|
172
211
|
|
|
173
212
|
.comty-sidepanel {
|
|
@@ -182,9 +221,9 @@ function getComtyStyle() {
|
|
|
182
221
|
display: flex;
|
|
183
222
|
flex-direction: column;
|
|
184
223
|
overflow: hidden;
|
|
185
|
-
background:
|
|
186
|
-
color:
|
|
187
|
-
border: 1px solid
|
|
224
|
+
background: var(--comty-surface);
|
|
225
|
+
color: var(--comty-foreground);
|
|
226
|
+
border: 1px solid var(--comty-border);
|
|
188
227
|
border-radius: 18px;
|
|
189
228
|
box-shadow: 0 1.5rem 4rem rgb(23 32 29 / 0.12);
|
|
190
229
|
font-family: sans-serif;
|
|
@@ -207,7 +246,7 @@ function getComtyStyle() {
|
|
|
207
246
|
align-items: center;
|
|
208
247
|
gap: 12px;
|
|
209
248
|
padding: 8px;
|
|
210
|
-
border-bottom: 1px solid
|
|
249
|
+
border-bottom: 1px solid var(--comty-border);
|
|
211
250
|
}
|
|
212
251
|
|
|
213
252
|
.comty-sidepanel-header-filters {
|
|
@@ -224,15 +263,15 @@ function getComtyStyle() {
|
|
|
224
263
|
height: 32px;
|
|
225
264
|
border: none;
|
|
226
265
|
border-radius: 8px;
|
|
227
|
-
background:
|
|
228
|
-
color:
|
|
266
|
+
background: var(--comty-field-surface);
|
|
267
|
+
color: var(--comty-foreground);
|
|
229
268
|
padding: 0 12px;
|
|
230
269
|
font: 14px/1 sans-serif;
|
|
231
270
|
outline: none;
|
|
232
271
|
}
|
|
233
272
|
|
|
234
273
|
.comty-sidepanel-search::placeholder {
|
|
235
|
-
color:
|
|
274
|
+
color: var(--comty-muted);
|
|
236
275
|
}
|
|
237
276
|
|
|
238
277
|
.comty-sidepanel-search::-webkit-search-cancel-button {
|
|
@@ -251,14 +290,14 @@ function getComtyStyle() {
|
|
|
251
290
|
border: none;
|
|
252
291
|
border-radius: 8px;
|
|
253
292
|
background: transparent;
|
|
254
|
-
color:
|
|
293
|
+
color: var(--comty-foreground);
|
|
255
294
|
cursor: pointer;
|
|
256
295
|
appearance: none;
|
|
257
296
|
}
|
|
258
297
|
|
|
259
298
|
.comty-sidepanel-icon-button:hover,
|
|
260
299
|
.comty-sidepanel-icon-button:focus-visible {
|
|
261
|
-
background:
|
|
300
|
+
background: var(--comty-field-surface);
|
|
262
301
|
outline: none;
|
|
263
302
|
}
|
|
264
303
|
|
|
@@ -284,7 +323,7 @@ function getComtyStyle() {
|
|
|
284
323
|
.comty-sidepanel-empty {
|
|
285
324
|
display: flex;
|
|
286
325
|
gap: 14px;
|
|
287
|
-
color:
|
|
326
|
+
color: var(--comty-foreground);
|
|
288
327
|
padding: 22px 12px;
|
|
289
328
|
font: 14px/1.45 sans-serif;
|
|
290
329
|
}
|
|
@@ -304,7 +343,7 @@ function getComtyStyle() {
|
|
|
304
343
|
border: none;
|
|
305
344
|
border-radius: 10px;
|
|
306
345
|
background: transparent;
|
|
307
|
-
color:
|
|
346
|
+
color: var(--comty-foreground);
|
|
308
347
|
font: 14px/1.35 sans-serif;
|
|
309
348
|
text-align: left;
|
|
310
349
|
cursor: pointer;
|
|
@@ -314,7 +353,7 @@ function getComtyStyle() {
|
|
|
314
353
|
|
|
315
354
|
.comty-sidepanel-item:hover,
|
|
316
355
|
.comty-sidepanel-item:focus-visible {
|
|
317
|
-
background:
|
|
356
|
+
background: var(--comty-field-surface);
|
|
318
357
|
outline: none;
|
|
319
358
|
}
|
|
320
359
|
|
|
@@ -342,7 +381,7 @@ function getComtyStyle() {
|
|
|
342
381
|
.comty-sidepanel-item-meta {
|
|
343
382
|
display: block;
|
|
344
383
|
margin-top: 6px;
|
|
345
|
-
color:
|
|
384
|
+
color: var(--comty-muted);
|
|
346
385
|
font: 11px/1.2 sans-serif;
|
|
347
386
|
overflow: hidden;
|
|
348
387
|
text-overflow: ellipsis;
|
|
@@ -394,9 +433,9 @@ function getComtyStyle() {
|
|
|
394
433
|
}
|
|
395
434
|
|
|
396
435
|
.comty-pin.comty-pin-with-avatar {
|
|
397
|
-
background:
|
|
398
|
-
color:
|
|
399
|
-
border: 2px solid
|
|
436
|
+
background: var(--comty-surface);
|
|
437
|
+
color: var(--comty-foreground);
|
|
438
|
+
border: 2px solid var(--comty-border);
|
|
400
439
|
box-shadow: 0 2px 6px rgba(0,0,0,.18);
|
|
401
440
|
filter: none;
|
|
402
441
|
transition:
|
|
@@ -444,9 +483,9 @@ function getComtyStyle() {
|
|
|
444
483
|
.comty-bubble {
|
|
445
484
|
position: fixed;
|
|
446
485
|
z-index: 2147483646;
|
|
447
|
-
background:
|
|
448
|
-
color:
|
|
449
|
-
border:
|
|
486
|
+
background: var(--comty-surface);
|
|
487
|
+
color: var(--comty-foreground);
|
|
488
|
+
border: 0;
|
|
450
489
|
box-shadow: 0 10px 28px rgba(0,0,0,.2);
|
|
451
490
|
padding: 0;
|
|
452
491
|
border-radius: 14px;
|
|
@@ -485,12 +524,12 @@ function getComtyStyle() {
|
|
|
485
524
|
gap: 12px;
|
|
486
525
|
min-height: 40px;
|
|
487
526
|
padding: 0 16px;
|
|
488
|
-
border-bottom: 1px solid
|
|
527
|
+
border-bottom: 1px solid var(--comty-border);
|
|
489
528
|
}
|
|
490
529
|
|
|
491
530
|
.comty-bubble-title {
|
|
492
531
|
margin: 0;
|
|
493
|
-
color:
|
|
532
|
+
color: var(--comty-foreground);
|
|
494
533
|
font-size: 12px;
|
|
495
534
|
font-weight: 600;
|
|
496
535
|
line-height: 1;
|
|
@@ -505,7 +544,7 @@ function getComtyStyle() {
|
|
|
505
544
|
|
|
506
545
|
.comty-bubble-content {
|
|
507
546
|
flex: 1;
|
|
508
|
-
color:
|
|
547
|
+
color: var(--comty-foreground);
|
|
509
548
|
font-size: 14px;
|
|
510
549
|
line-height: 1.45;
|
|
511
550
|
overflow-wrap: anywhere;
|
|
@@ -522,7 +561,7 @@ function getComtyStyle() {
|
|
|
522
561
|
border: none;
|
|
523
562
|
padding: 0;
|
|
524
563
|
background: transparent;
|
|
525
|
-
color:
|
|
564
|
+
color: var(--comty-foreground);
|
|
526
565
|
cursor: text;
|
|
527
566
|
font: 14px/1.45 Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
528
567
|
outline: none;
|
|
@@ -547,7 +586,7 @@ function getComtyStyle() {
|
|
|
547
586
|
overflow-y: auto;
|
|
548
587
|
overscroll-behavior: contain;
|
|
549
588
|
scrollbar-width: thin;
|
|
550
|
-
scrollbar-color:
|
|
589
|
+
scrollbar-color: var(--comty-hover-surface) transparent;
|
|
551
590
|
box-sizing: border-box;
|
|
552
591
|
}
|
|
553
592
|
|
|
@@ -579,7 +618,7 @@ function getComtyStyle() {
|
|
|
579
618
|
.comty-comment-author-name {
|
|
580
619
|
min-width: 0;
|
|
581
620
|
overflow: hidden;
|
|
582
|
-
color:
|
|
621
|
+
color: var(--comty-foreground);
|
|
583
622
|
font-size: 14px;
|
|
584
623
|
font-weight: 600;
|
|
585
624
|
line-height: 1.25;
|
|
@@ -589,7 +628,7 @@ function getComtyStyle() {
|
|
|
589
628
|
|
|
590
629
|
.comty-comment-time {
|
|
591
630
|
flex: 0 0 auto;
|
|
592
|
-
color:
|
|
631
|
+
color: var(--comty-muted);
|
|
593
632
|
font-size: 13px;
|
|
594
633
|
font-weight: 400;
|
|
595
634
|
line-height: 1.25;
|
|
@@ -602,8 +641,8 @@ function getComtyStyle() {
|
|
|
602
641
|
height: 24px;
|
|
603
642
|
overflow: hidden;
|
|
604
643
|
border-radius: 50%;
|
|
605
|
-
background:
|
|
606
|
-
color:
|
|
644
|
+
background: var(--comty-field-surface);
|
|
645
|
+
color: var(--comty-foreground);
|
|
607
646
|
font-size: 10px;
|
|
608
647
|
font-weight: 600;
|
|
609
648
|
line-height: 1;
|
|
@@ -639,7 +678,7 @@ function getComtyStyle() {
|
|
|
639
678
|
border: 0;
|
|
640
679
|
border-radius: 6px;
|
|
641
680
|
background: transparent;
|
|
642
|
-
color:
|
|
681
|
+
color: var(--comty-foreground);
|
|
643
682
|
cursor: pointer;
|
|
644
683
|
appearance: none;
|
|
645
684
|
transition:
|
|
@@ -649,7 +688,7 @@ function getComtyStyle() {
|
|
|
649
688
|
|
|
650
689
|
.comty-icon-button:hover,
|
|
651
690
|
.comty-icon-button:focus-visible {
|
|
652
|
-
background:
|
|
691
|
+
background: var(--comty-hover-surface);
|
|
653
692
|
outline: none;
|
|
654
693
|
}
|
|
655
694
|
|
|
@@ -658,7 +697,7 @@ function getComtyStyle() {
|
|
|
658
697
|
}
|
|
659
698
|
|
|
660
699
|
.comty-icon-button:disabled {
|
|
661
|
-
color:
|
|
700
|
+
color: var(--comty-muted);
|
|
662
701
|
cursor: default;
|
|
663
702
|
}
|
|
664
703
|
|
|
@@ -681,7 +720,7 @@ function getComtyStyle() {
|
|
|
681
720
|
border: 0;
|
|
682
721
|
border-radius: 6px;
|
|
683
722
|
background: transparent;
|
|
684
|
-
color:
|
|
723
|
+
color: var(--comty-foreground);
|
|
685
724
|
cursor: pointer;
|
|
686
725
|
appearance: none;
|
|
687
726
|
font: 600 12px/1 Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
@@ -692,7 +731,7 @@ function getComtyStyle() {
|
|
|
692
731
|
|
|
693
732
|
.comty-status-trigger:hover,
|
|
694
733
|
.comty-status-trigger:focus-visible {
|
|
695
|
-
background:
|
|
734
|
+
background: var(--comty-hover-surface);
|
|
696
735
|
outline: none;
|
|
697
736
|
}
|
|
698
737
|
|
|
@@ -701,7 +740,7 @@ function getComtyStyle() {
|
|
|
701
740
|
}
|
|
702
741
|
|
|
703
742
|
.comty-status-trigger:disabled {
|
|
704
|
-
color:
|
|
743
|
+
color: var(--comty-muted);
|
|
705
744
|
cursor: default;
|
|
706
745
|
}
|
|
707
746
|
|
|
@@ -728,12 +767,12 @@ function getComtyStyle() {
|
|
|
728
767
|
}
|
|
729
768
|
|
|
730
769
|
.comty-popover-item.comty-popover-item-destructive {
|
|
731
|
-
color:
|
|
770
|
+
color: var(--comty-agent-error);
|
|
732
771
|
}
|
|
733
772
|
|
|
734
773
|
.comty-popover-item.comty-popover-item-destructive:hover,
|
|
735
774
|
.comty-popover-item.comty-popover-item-destructive:focus-visible {
|
|
736
|
-
background:
|
|
775
|
+
background: color-mix(in srgb, var(--comty-agent-error) 12%, var(--comty-surface));
|
|
737
776
|
outline: none;
|
|
738
777
|
}
|
|
739
778
|
|
|
@@ -751,7 +790,7 @@ function getComtyStyle() {
|
|
|
751
790
|
min-height: 36px;
|
|
752
791
|
padding: 4px 8px 4px 16px;
|
|
753
792
|
border-radius: 14px;
|
|
754
|
-
background:
|
|
793
|
+
background: var(--comty-field-surface);
|
|
755
794
|
box-sizing: border-box;
|
|
756
795
|
transition: box-shadow 160ms ease;
|
|
757
796
|
}
|
|
@@ -766,13 +805,13 @@ function getComtyStyle() {
|
|
|
766
805
|
padding: 0;
|
|
767
806
|
border: 0;
|
|
768
807
|
background: transparent;
|
|
769
|
-
color:
|
|
808
|
+
color: var(--comty-foreground);
|
|
770
809
|
font: 14px/1.3 Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
771
810
|
outline: none;
|
|
772
811
|
}
|
|
773
812
|
|
|
774
813
|
.comty-reply-input::placeholder {
|
|
775
|
-
color:
|
|
814
|
+
color: var(--comty-muted);
|
|
776
815
|
}
|
|
777
816
|
|
|
778
817
|
.comty-reply-button {
|
|
@@ -802,7 +841,7 @@ function getComtyStyle() {
|
|
|
802
841
|
}
|
|
803
842
|
|
|
804
843
|
.comty-reply-button:disabled {
|
|
805
|
-
background:
|
|
844
|
+
background: var(--comty-hover-surface);
|
|
806
845
|
cursor: default;
|
|
807
846
|
}
|
|
808
847
|
|
|
@@ -820,7 +859,7 @@ function getComtyStyle() {
|
|
|
820
859
|
padding: 4px 16px;
|
|
821
860
|
border: 0;
|
|
822
861
|
border-radius: 14px;
|
|
823
|
-
background:
|
|
862
|
+
background: var(--comty-field-surface);
|
|
824
863
|
color: #0d99ff;
|
|
825
864
|
text-align: left;
|
|
826
865
|
font: 600 14px/1.3 Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
@@ -828,7 +867,7 @@ function getComtyStyle() {
|
|
|
828
867
|
transition: background-color 160ms ease;
|
|
829
868
|
}
|
|
830
869
|
|
|
831
|
-
.comty-sign-in-reply:hover { background:
|
|
870
|
+
.comty-sign-in-reply:hover { background: var(--comty-hover-surface); }
|
|
832
871
|
.comty-sign-in-reply:focus-visible { outline: 2px solid #0d99ff; outline-offset: 2px; }
|
|
833
872
|
|
|
834
873
|
.comty-hover-bubble {
|
|
@@ -872,15 +911,32 @@ function getComtyStyle() {
|
|
|
872
911
|
font-size: 12px;
|
|
873
912
|
}
|
|
874
913
|
|
|
875
|
-
.comty-auth-backdrop { position: fixed; inset: 0; z-index: 2147483647; display: grid; place-items: center; padding:
|
|
876
|
-
.comty-auth-dialog { width: min(
|
|
877
|
-
.comty-auth-dialog
|
|
878
|
-
.comty-auth-
|
|
879
|
-
.comty-auth-
|
|
880
|
-
.comty-auth-
|
|
881
|
-
.comty-auth-
|
|
882
|
-
.comty-auth-
|
|
883
|
-
.comty-auth-
|
|
914
|
+
.comty-auth-backdrop { position: fixed; inset: 0; z-index: 2147483647; display: grid; place-items: center; padding: 1rem; background: var(--comty-dialog-backdrop); font-family: var(--comty-font); }
|
|
915
|
+
.comty-auth-dialog { box-sizing: border-box; width: min(var(--comty-dialog-width), 100%); max-height: calc(100dvh - 2rem); overflow: auto; padding: 1.75rem; border: 0; border-radius: var(--comty-dialog-radius); background: var(--comty-dialog-surface); color: var(--comty-dialog-ink); box-shadow: var(--comty-dialog-shadow); text-align: left; outline: none; }
|
|
916
|
+
.comty-auth-dialog *, .comty-auth-dialog *::before, .comty-auth-dialog *::after { box-sizing: border-box; }
|
|
917
|
+
.comty-auth-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; }
|
|
918
|
+
.comty-auth-icon, .comty-auth-close { display: flex; align-items: center; justify-content: center; width: 2.5rem; height: 2.5rem; padding: 0; border: 0; background: var(--comty-dialog-field); color: var(--comty-dialog-ink); }
|
|
919
|
+
.comty-auth-icon { border-radius: .75rem; }
|
|
920
|
+
.comty-auth-close { border-radius: 50%; cursor: pointer; }
|
|
921
|
+
.comty-auth-header svg { width: 1.25rem; height: 1.25rem; }
|
|
922
|
+
.comty-auth-dialog h2 { margin: 0; color: inherit; font: 600 1.5rem/1.25 var(--comty-font); letter-spacing: -.025em; }
|
|
923
|
+
.comty-auth-dialog p { margin: 1rem 0 0; color: var(--comty-dialog-soft); font: 400 .875rem/1.65 var(--comty-font); }
|
|
924
|
+
.comty-auth-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: .5rem; margin-top: 1.75rem; padding-top: 1.5rem; border-top: .0625rem solid var(--comty-dialog-line); }
|
|
925
|
+
.comty-auth-actions button { display: inline-flex; align-items: center; justify-content: center; min-height: 2.5rem; margin: 0; padding: .625rem 1rem; border: 0; border-radius: 999rem; background: transparent; color: var(--comty-dialog-soft); font: 500 .875rem/1.25 var(--comty-font); cursor: pointer; }
|
|
926
|
+
.comty-auth-actions button:hover, .comty-auth-close:hover { background: var(--comty-dialog-field); color: var(--comty-dialog-ink); }
|
|
927
|
+
.comty-auth-actions .comty-auth-primary { border-color: transparent; background: var(--comty-dialog-ink); color: var(--comty-dialog-surface); }
|
|
928
|
+
.comty-auth-actions .comty-auth-primary:hover { background: var(--comty-dialog-soft); color: var(--comty-dialog-surface); }
|
|
929
|
+
.comty-auth-dialog button:focus-visible { outline: .125rem solid var(--comty-dialog-ink); outline-offset: .1875rem; }
|
|
930
|
+
.comty-auth-dialog button:disabled { opacity: .5; cursor: wait; }
|
|
931
|
+
.comty-distance-layer { position: fixed; inset: 0; z-index: 1; width: 100%; height: 100%; overflow: visible; pointer-events: none; color: var(--comty-measure); }
|
|
932
|
+
.comty-distance-line, .comty-distance-extension { stroke: currentColor; stroke-width: 1; fill: none; shape-rendering: crispEdges; }
|
|
933
|
+
.comty-distance-extension { stroke-dasharray: 3 3; opacity: .65; }
|
|
934
|
+
.comty-distance-arrow, .comty-distance-label, .comty-distance-snap { fill: currentColor; }
|
|
935
|
+
.comty-distance-value { fill: var(--comty-paper); font: 500 .75rem var(--comty-font); }
|
|
936
|
+
.comty-distance-announcement { position: absolute; width: .0625rem; height: .0625rem; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
|
937
|
+
html.comty-measure-vertical, html.comty-measure-vertical body *:not(#comty-root):not(#comty-root *) { cursor: ns-resize !important; }
|
|
938
|
+
html.comty-measure-horizontal, html.comty-measure-horizontal body *:not(#comty-root):not(#comty-root *) { cursor: ew-resize !important; }
|
|
939
|
+
.comty-auth-toast { position: fixed; top: 16px; left: 50%; z-index: 2147483647; transform: translateX(-50%); padding: 9px 12px; border: 0; border-radius: 8px; background: var(--comty-surface); color: var(--comty-foreground); box-shadow: 0 8px 24px rgba(0,0,0,.15); font: 600 12px/1 sans-serif; }
|
|
884
940
|
.comty-auth-toast button { margin-left: .5rem; padding: .375rem .5rem; border: 0; border-radius: .375rem; background: var(--comty-accent); color: var(--comty-paper); cursor: pointer; font: inherit; }
|
|
885
941
|
|
|
886
942
|
.comty-close {
|
|
@@ -893,7 +949,7 @@ function getComtyStyle() {
|
|
|
893
949
|
border: none;
|
|
894
950
|
border-radius: 6px;
|
|
895
951
|
background: transparent;
|
|
896
|
-
color:
|
|
952
|
+
color: var(--comty-muted);
|
|
897
953
|
cursor: pointer;
|
|
898
954
|
appearance: none;
|
|
899
955
|
font: 18px/1 sans-serif;
|
|
@@ -901,8 +957,8 @@ function getComtyStyle() {
|
|
|
901
957
|
|
|
902
958
|
.comty-close:hover,
|
|
903
959
|
.comty-close:focus-visible {
|
|
904
|
-
background:
|
|
905
|
-
color:
|
|
960
|
+
background: var(--comty-field-surface);
|
|
961
|
+
color: var(--comty-foreground);
|
|
906
962
|
outline: none;
|
|
907
963
|
}
|
|
908
964
|
|
|
@@ -919,10 +975,10 @@ function getComtyStyle() {
|
|
|
919
975
|
max-width: 100%;
|
|
920
976
|
min-height: 18px;
|
|
921
977
|
padding: 2px 7px;
|
|
922
|
-
border:
|
|
978
|
+
border: 0;
|
|
923
979
|
border-radius: 6px;
|
|
924
|
-
background:
|
|
925
|
-
color:
|
|
980
|
+
background: var(--comty-field-surface);
|
|
981
|
+
color: var(--comty-muted);
|
|
926
982
|
font-size: 11px;
|
|
927
983
|
font-weight: 500;
|
|
928
984
|
line-height: 1;
|
|
@@ -938,11 +994,11 @@ function getComtyStyle() {
|
|
|
938
994
|
gap: 8px;
|
|
939
995
|
margin-top: 12px;
|
|
940
996
|
padding-top: 10px;
|
|
941
|
-
border-top: 1px solid
|
|
997
|
+
border-top: 1px solid var(--comty-border);
|
|
942
998
|
}
|
|
943
999
|
|
|
944
1000
|
.comty-status-label {
|
|
945
|
-
color:
|
|
1001
|
+
color: var(--comty-muted);
|
|
946
1002
|
font-size: 11px;
|
|
947
1003
|
font-weight: 600;
|
|
948
1004
|
}
|
|
@@ -967,10 +1023,10 @@ function getComtyStyle() {
|
|
|
967
1023
|
align-items: center;
|
|
968
1024
|
gap: 6px;
|
|
969
1025
|
min-height: 30px;
|
|
970
|
-
border:
|
|
1026
|
+
border: 0;
|
|
971
1027
|
border-radius: 7px;
|
|
972
|
-
background:
|
|
973
|
-
color:
|
|
1028
|
+
background: var(--comty-surface);
|
|
1029
|
+
color: var(--comty-foreground);
|
|
974
1030
|
padding: 4px 8px;
|
|
975
1031
|
font: 600 12px/1.2 sans-serif;
|
|
976
1032
|
cursor: pointer;
|
|
@@ -978,8 +1034,8 @@ function getComtyStyle() {
|
|
|
978
1034
|
|
|
979
1035
|
.comty-status-button:hover,
|
|
980
1036
|
.comty-status-button:focus-visible {
|
|
981
|
-
border-color:
|
|
982
|
-
background:
|
|
1037
|
+
border-color: var(--comty-muted);
|
|
1038
|
+
background: var(--comty-hover-surface);
|
|
983
1039
|
outline: none;
|
|
984
1040
|
}
|
|
985
1041
|
|
|
@@ -1002,8 +1058,8 @@ function getComtyStyle() {
|
|
|
1002
1058
|
.comty-popover {
|
|
1003
1059
|
position: fixed;
|
|
1004
1060
|
z-index: 2147483646;
|
|
1005
|
-
background:
|
|
1006
|
-
border:
|
|
1061
|
+
background: var(--comty-surface);
|
|
1062
|
+
border: 0;
|
|
1007
1063
|
box-shadow: 0 4px 20px rgba(0,0,0,.1);
|
|
1008
1064
|
border-radius: 8px;
|
|
1009
1065
|
padding: 4px;
|
|
@@ -1030,7 +1086,7 @@ function getComtyStyle() {
|
|
|
1030
1086
|
border-radius: 6px;
|
|
1031
1087
|
cursor: pointer;
|
|
1032
1088
|
font-size: 13px;
|
|
1033
|
-
color:
|
|
1089
|
+
color: var(--comty-foreground);
|
|
1034
1090
|
text-align: left;
|
|
1035
1091
|
position: relative;
|
|
1036
1092
|
}
|
|
@@ -1040,25 +1096,25 @@ function getComtyStyle() {
|
|
|
1040
1096
|
right: 8px;
|
|
1041
1097
|
width: 14px;
|
|
1042
1098
|
height: 14px;
|
|
1043
|
-
color:
|
|
1099
|
+
color: var(--comty-foreground);
|
|
1044
1100
|
}
|
|
1045
1101
|
|
|
1046
1102
|
.comty-popover-item:hover {
|
|
1047
|
-
background:
|
|
1103
|
+
background: var(--comty-field-surface);
|
|
1048
1104
|
}
|
|
1049
1105
|
|
|
1050
1106
|
.comty-popover-header {
|
|
1051
1107
|
padding: 6px 8px;
|
|
1052
1108
|
font-size: 11px;
|
|
1053
1109
|
font-weight: 600;
|
|
1054
|
-
color:
|
|
1110
|
+
color: var(--comty-muted);
|
|
1055
1111
|
text-transform: uppercase;
|
|
1056
1112
|
letter-spacing: 0.05em;
|
|
1057
1113
|
}
|
|
1058
1114
|
|
|
1059
1115
|
.comty-popover-divider {
|
|
1060
1116
|
height: 1px;
|
|
1061
|
-
background:
|
|
1117
|
+
background: var(--comty-border);
|
|
1062
1118
|
margin: 4px 0;
|
|
1063
1119
|
}
|
|
1064
1120
|
|
|
@@ -1080,9 +1136,14 @@ function getComtyStyle() {
|
|
|
1080
1136
|
.comty-layout-guide { display: grid; box-sizing: border-box; }
|
|
1081
1137
|
.comty-layout-guide > span { background: var(--guide-color); min-width: 0; min-height: 0; }
|
|
1082
1138
|
.comty-layout-grid { background-image: linear-gradient(to right, var(--guide-color) 1px, transparent 1px), linear-gradient(to bottom, var(--guide-color) 1px, transparent 1px); }
|
|
1083
|
-
.comty-tool-panel { position: fixed; right: 1rem; bottom: 5rem; width: 18rem; max-width: calc(100vw - 2rem); max-height: calc(100vh - 7rem); overflow: auto; box-sizing: border-box; padding: .75rem; border:
|
|
1139
|
+
.comty-tool-panel { position: fixed; right: 1rem; bottom: 5rem; width: 18rem; max-width: calc(100vw - 2rem); max-height: calc(100vh - 7rem); overflow: auto; box-sizing: border-box; padding: .75rem; border: 0; border-radius: .75rem; background: var(--comty-surface); color: var(--comty-foreground); box-shadow: var(--comty-floating-shadow); font: .8125rem/1.5 ui-sans-serif, system-ui, sans-serif; pointer-events: auto; z-index: 2147483647; }
|
|
1084
1140
|
.comty-tool-panel-header, .comty-guide-row { display: flex; align-items: center; gap: .5rem; }
|
|
1085
1141
|
.comty-tool-panel-header { margin-bottom: .5rem; }
|
|
1142
|
+
.comty-tool-panel .comty-guide-select { justify-content: space-between; gap: .75rem; min-width: 6.5rem; padding: .375rem .5rem; border: 0; border-radius: .375rem; background: var(--comty-field-surface); }
|
|
1143
|
+
.comty-guide-select:focus-visible, .comty-guide-popover .comty-popover-item:focus-visible { outline: .125rem solid var(--comty-accent); outline-offset: .125rem; }
|
|
1144
|
+
.comty-guide-popover .comty-popover-item { justify-content: flex-start; padding: .375rem 2rem .375rem .5rem; }
|
|
1145
|
+
.comty-guide-popover .comty-popover-item-check svg { width: 100%; height: 100%; }
|
|
1146
|
+
.comty-guide-align { display: flex; align-items: center; justify-content: space-between; gap: .5rem; }
|
|
1086
1147
|
.comty-tool-panel-header strong { flex: 1; white-space: nowrap; }
|
|
1087
1148
|
.comty-tool-panel button { display: inline-flex; align-items: center; justify-content: center; gap: .5rem; padding: .375rem; border: 0; border-radius: .375rem; color: inherit; background: transparent; font: inherit; cursor: pointer; }
|
|
1088
1149
|
.comty-tool-panel button:hover { background: var(--comty-border); }
|
|
@@ -1093,9 +1154,9 @@ function getComtyStyle() {
|
|
|
1093
1154
|
.comty-guide-item + .comty-guide-item { margin-top: .375rem; }
|
|
1094
1155
|
.comty-guide-fields { display: grid; gap: .5rem; margin-block: .5rem; padding: .5rem; border-radius: .375rem; background: var(--comty-field-surface); }
|
|
1095
1156
|
.comty-guide-fields label { display: flex; align-items: center; justify-content: space-between; gap: .5rem; }
|
|
1096
|
-
.comty-tool-panel input, .comty-tool-panel select { min-width: 0; width: 6rem; box-sizing: border-box; border:
|
|
1157
|
+
.comty-tool-panel input, .comty-tool-panel select { min-width: 0; width: 6rem; box-sizing: border-box; border: 0; border-radius: .25rem; padding: .25rem; background: var(--comty-surface); color: var(--comty-foreground); font: inherit; }
|
|
1097
1158
|
.comty-tool-panel input[type="color"] { height: 1.75rem; padding: .125rem; }
|
|
1098
|
-
.comty-ruler { position: fixed; background: var(--comty-
|
|
1159
|
+
.comty-ruler { position: fixed; background: var(--comty-surface); pointer-events: auto; touch-action: none; }
|
|
1099
1160
|
.comty-ruler-horizontal { left: 0; top: 0; width: 100vw; height: 1.125rem; border-bottom: .0625rem solid var(--comty-border); cursor: ns-resize; }
|
|
1100
1161
|
.comty-ruler-vertical { left: 0; top: 0; height: 100vh; width: 1.125rem; border-right: .0625rem solid var(--comty-border); cursor: ew-resize; }
|
|
1101
1162
|
.comty-ruler-guide { position: fixed; padding: 0; margin: 0; border: 0; background: transparent; color: var(--comty-outline); pointer-events: auto; touch-action: none; }
|
|
@@ -1109,14 +1170,43 @@ function getComtyStyle() {
|
|
|
1109
1170
|
.comty-ruler-guide span { position: absolute; padding: .125rem .375rem; border-radius: .25rem; color: var(--comty-paper); background: var(--comty-ink); font: .625rem/1.5 ui-monospace, monospace; }
|
|
1110
1171
|
.comty-ruler-guide-horizontal span { left: 1.5rem; top: .5rem; }
|
|
1111
1172
|
.comty-ruler-guide-vertical span { top: 1.5rem; left: .5rem; }
|
|
1112
|
-
.comty-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
.comty-
|
|
1116
|
-
.comty-
|
|
1117
|
-
.comty-color-
|
|
1118
|
-
.comty-color-
|
|
1119
|
-
.comty-color-
|
|
1173
|
+
.comty-property-panel { position: fixed; z-index: 2147483646; width: max-content; max-width: calc(100vw - 1rem); max-height: calc(100vh - 1rem); box-sizing: border-box; margin: 0; padding: .375rem; border: 0; border-radius: 1rem; background: var(--comty-toolbar-group); color: var(--comty-foreground); box-shadow: var(--comty-floating-shadow); font: var(--comty-property-font-size)/1.4 var(--comty-font); pointer-events: auto; overflow-x: hidden; overflow-y: auto; }
|
|
1174
|
+
html[data-comty-property-picking="color"], html[data-comty-property-picking="color"] body, html[data-comty-property-picking="color"] body :not(#comty-root):not(#comty-root *) { cursor: none !important; }
|
|
1175
|
+
html[data-comty-property-picking="font"] body :not(#comty-root):not(#comty-root *) { cursor: url("data:image/svg+xml,${encodeURIComponent(typographyCursor)}") 2 2, default !important; }
|
|
1176
|
+
.comty-property-panel[data-expanded] { width: var(--comty-property-width); }
|
|
1177
|
+
.comty-property-panel[hidden] { display: none; }
|
|
1178
|
+
.comty-color-magnifier { --comty-lens-rim: var(--comty-paper); --comty-lens-line: var(--comty-magnifier-gray); position: fixed; z-index: 2147483647; width: var(--comty-magnifier-size); height: var(--comty-magnifier-size); padding: var(--comty-magnifier-rim); box-sizing: border-box; border-radius: 50%; background: var(--comty-lens-rim); box-shadow: var(--comty-magnifier-shadow); transform: translate(-50%, -50%); pointer-events: none; }
|
|
1179
|
+
.comty-color-magnifier[data-tone="dark"] { --comty-lens-rim: var(--comty-magnifier-dark); --comty-lens-line: var(--comty-paper); }
|
|
1180
|
+
.comty-color-magnifier-glass { position: relative; width: 100%; height: 100%; box-sizing: border-box; overflow: hidden; border: var(--comty-magnifier-line) solid var(--comty-lens-line); border-radius: 50%; }
|
|
1181
|
+
.comty-color-magnifier canvas { display: block; width: 100%; height: 100%; image-rendering: pixelated; }
|
|
1182
|
+
.comty-color-magnifier-glass::after { content: ""; position: absolute; inset: 0; background-image: linear-gradient(to right, var(--comty-lens-line) var(--comty-magnifier-grid-line), transparent var(--comty-magnifier-grid-line)), linear-gradient(to bottom, var(--comty-lens-line) var(--comty-magnifier-grid-line), transparent var(--comty-magnifier-grid-line)); background-size: calc(100% / 9) calc(100% / 9); opacity: .8; }
|
|
1183
|
+
.comty-color-magnifier-glass > span { position: absolute; z-index: 1; left: 50%; top: 50%; width: calc(100% / 9); height: calc(100% / 9); box-sizing: content-box; border: var(--comty-magnifier-line) solid var(--comty-lens-line); border-radius: .125rem; transform: translate(-50%, -50%); }
|
|
1184
|
+
.comty-property-panel button { display: inline-flex; align-items: center; justify-content: center; min-width: 0; padding: 0; border: 0; background: transparent; color: inherit; font: inherit; cursor: pointer; }
|
|
1185
|
+
.comty-property-panel button:focus-visible { outline: .0625rem solid var(--comty-accent); outline-offset: .0625rem; }
|
|
1186
|
+
.comty-property-detail { margin-bottom: .375rem; }
|
|
1187
|
+
.comty-property-rows { padding: .25rem .625rem .375rem; border-radius: .5rem; background: var(--comty-toolbar-surface); }
|
|
1188
|
+
.comty-property-row { display: grid; grid-template-columns: 3.5rem minmax(0, 1fr); align-items: center; gap: .5rem; min-height: var(--comty-property-row-height); }
|
|
1189
|
+
.comty-property-row > span { color: var(--comty-muted); }
|
|
1190
|
+
.comty-property-row button { justify-content: space-between; gap: .5rem; text-align: left; }
|
|
1191
|
+
.comty-property-row button > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-variant-numeric: tabular-nums; }
|
|
1192
|
+
.comty-property-row button[aria-label^="Copy Colour"] > span::before { content: ""; display: inline-block; width: .75rem; height: .75rem; margin-right: .375rem; border: .0625rem solid var(--comty-border); border-radius: .1875rem; background: var(--comty-picked-color); vertical-align: -.0625rem; }
|
|
1193
|
+
.comty-property-copy-icon { position: relative; flex: 0 0 .75rem; width: .75rem; height: .75rem; color: var(--comty-muted); opacity: .65; }
|
|
1194
|
+
.comty-property-row button:hover .comty-property-copy-icon, .comty-property-row button[data-copied] .comty-property-copy-icon { opacity: 1; }
|
|
1195
|
+
.comty-property-copy-icon svg { position: absolute; inset: 0; width: 100%; height: 100%; transition: opacity 150ms var(--comty-ease-out), transform 150ms var(--comty-ease-out); }
|
|
1196
|
+
.comty-property-copy-icon svg:last-child { opacity: 0; transform: scale(.5); }
|
|
1197
|
+
.comty-property-row button[data-copied] svg:first-child { opacity: 0; transform: scale(.5); }
|
|
1198
|
+
.comty-property-row button[data-copied] svg:last-child { opacity: 1; transform: scale(1); }
|
|
1199
|
+
@media (prefers-reduced-motion: reduce) { .comty-property-copy-icon svg { transition: none; } }
|
|
1200
|
+
.comty-property-row button[disabled] { cursor: default; opacity: .5; }
|
|
1201
|
+
.comty-property-strip { display: flex; align-items: center; justify-content: center; gap: .375rem; }
|
|
1202
|
+
.comty-property-recent, .comty-property-empty { flex: 0 0 var(--comty-property-swatch); width: var(--comty-property-swatch); height: var(--comty-property-swatch); box-sizing: border-box; border-radius: .5rem; }
|
|
1203
|
+
.comty-property-panel .comty-property-recent { border: .0625rem solid var(--comty-border); font: 600 .875rem/1 var(--comty-font); }
|
|
1204
|
+
.comty-property-panel[data-kind="font"] .comty-property-recent { background: var(--comty-toolbar-surface); }
|
|
1205
|
+
.comty-property-panel .comty-property-recent[aria-pressed="true"] { outline: .0625rem solid var(--comty-muted); outline-offset: .0625rem; }
|
|
1206
|
+
.comty-property-panel .comty-property-recent[aria-pressed="true"]:focus-visible { outline-color: var(--comty-accent); }
|
|
1207
|
+
.comty-property-empty { border: .0625rem dashed var(--comty-border); }
|
|
1208
|
+
.comty-font-target { position: fixed; box-sizing: border-box; border: .0625rem solid var(--comty-accent); pointer-events: none; }
|
|
1209
|
+
.comty-property-feedback { display: block; height: 0; overflow: hidden; }
|
|
1120
1210
|
.comty-motion-panel { width: 17.5rem; max-width: calc(100vw - 1rem); padding: var(--comty-toolbar-padding); border: 0; border-radius: .9375rem; background: var(--comty-surface); color: var(--comty-foreground); }
|
|
1121
1211
|
.comty-motion-panel [hidden] { display: none !important; }
|
|
1122
1212
|
.comty-motion-row { display: flex; align-items: center; gap: .375rem; }
|
|
@@ -1154,11 +1244,11 @@ function getComtyStyle() {
|
|
|
1154
1244
|
.comty-inspect-margin { --comty-spacing-color: var(--comty-margin-fill); }
|
|
1155
1245
|
.comty-inspect-padding { --comty-spacing-color: var(--comty-padding-fill); }
|
|
1156
1246
|
.comty-inspect-gap { --comty-spacing-color: var(--comty-gap-fill); }
|
|
1157
|
-
.comty-inspect-spacing span { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); padding-inline: .2rem; border-radius: .2rem; background: var(--comty-spacing-color); color: var(--comty-
|
|
1247
|
+
.comty-inspect-spacing span { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); padding-inline: .2rem; border-radius: .2rem; background: var(--comty-spacing-color); color: var(--comty-foreground); font: 600 .625rem/1.3 ui-monospace, monospace; white-space: nowrap; }
|
|
1158
1248
|
.comty-inspector {
|
|
1159
1249
|
position: fixed; width: 15rem; max-width: calc(100vw - 1rem);
|
|
1160
1250
|
padding-block: .5rem; border-radius: .5rem;
|
|
1161
|
-
color: var(--comty-
|
|
1251
|
+
color: var(--comty-foreground); background: var(--comty-surface);
|
|
1162
1252
|
box-shadow: var(--comty-floating-shadow); font: .625rem/1.5 ui-sans-serif, system-ui, sans-serif;
|
|
1163
1253
|
pointer-events: none; box-sizing: border-box;
|
|
1164
1254
|
}
|
|
@@ -1173,6 +1263,8 @@ function getComtyStyle() {
|
|
|
1173
1263
|
.comty-inspector button:focus-visible { outline: .125rem solid var(--comty-accent); outline-offset: .125rem; }
|
|
1174
1264
|
.comty-inspector-value { display: flex; flex-direction: column; align-items: flex-end; min-width: 0; }
|
|
1175
1265
|
.comty-inspector-value > button, .comty-inspector-tokens button { max-width: 100%; }
|
|
1266
|
+
.comty-inspector-picker { display: inline-flex; align-items: center; gap: .25rem; }
|
|
1267
|
+
.comty-inspector-swatch { display: inline-block; flex: 0 0 .625rem; width: .625rem; height: .625rem; border: .0625rem solid var(--comty-border); border-radius: .1875rem; }
|
|
1176
1268
|
.comty-inspector-tokens { display: flex; flex-direction: column; align-items: flex-end; max-width: 100%; }
|
|
1177
1269
|
.comty-inspector-tokens button { font-family: ui-monospace, monospace; color: var(--comty-muted); }
|
|
1178
1270
|
.comty-inspector-details { max-height: 50vh; overflow-y: auto; }
|
|
@@ -1183,8 +1275,8 @@ function getComtyStyle() {
|
|
|
1183
1275
|
.comty-create-bubble {
|
|
1184
1276
|
position: fixed;
|
|
1185
1277
|
z-index: 2147483647;
|
|
1186
|
-
background:
|
|
1187
|
-
border:
|
|
1278
|
+
background: var(--comty-surface);
|
|
1279
|
+
border: 0;
|
|
1188
1280
|
box-shadow: 0 4px 20px rgba(0,0,0,.1);
|
|
1189
1281
|
border-radius: 999px;
|
|
1190
1282
|
width: 320px;
|
|
@@ -1218,13 +1310,13 @@ function getComtyStyle() {
|
|
|
1218
1310
|
outline: none;
|
|
1219
1311
|
background: transparent;
|
|
1220
1312
|
overflow-y: auto;
|
|
1221
|
-
color:
|
|
1313
|
+
color: var(--comty-foreground);
|
|
1222
1314
|
scrollbar-width: thin;
|
|
1223
1315
|
scrollbar-color: gray transparent;
|
|
1224
1316
|
}
|
|
1225
1317
|
|
|
1226
1318
|
.comty-create-textarea::placeholder {
|
|
1227
|
-
color:
|
|
1319
|
+
color: var(--comty-muted);
|
|
1228
1320
|
}
|
|
1229
1321
|
|
|
1230
1322
|
.comty-create-actions {
|
|
@@ -1241,7 +1333,7 @@ function getComtyStyle() {
|
|
|
1241
1333
|
height: 32px;
|
|
1242
1334
|
padding: 0;
|
|
1243
1335
|
border-radius: 50%;
|
|
1244
|
-
background:
|
|
1336
|
+
background: var(--comty-border);
|
|
1245
1337
|
color: white;
|
|
1246
1338
|
border: none;
|
|
1247
1339
|
cursor: pointer;
|
|
@@ -2097,7 +2189,7 @@ function createScreenshotCapture() {
|
|
|
2097
2189
|
|
|
2098
2190
|
const clonedBody = clone.querySelector("body");
|
|
2099
2191
|
if (clonedBody instanceof HTMLElement) {
|
|
2100
|
-
clonedBody.style.width =
|
|
2192
|
+
clonedBody.style.width = getComputedStyle(document.body).width;
|
|
2101
2193
|
clonedBody.style.minHeight = `${documentHeight}px`;
|
|
2102
2194
|
clonedBody.style.margin = clonedBody.style.margin || "0";
|
|
2103
2195
|
}
|
|
@@ -2128,6 +2220,109 @@ function createScreenshotCapture() {
|
|
|
2128
2220
|
return { waitForPaint, waitForCustomElements, captureViewportScreenshot };
|
|
2129
2221
|
}
|
|
2130
2222
|
|
|
2223
|
+
// Page-local counterpart of the native eyedropper; never captures outside the page.
|
|
2224
|
+
function createColorMagnifier({ root, isComtyElement, capture }) {
|
|
2225
|
+
const lens = document.createElement('div');
|
|
2226
|
+
lens.className = 'comty-color-magnifier';
|
|
2227
|
+
lens.setAttribute('aria-hidden', 'true');
|
|
2228
|
+
lens.hidden = true;
|
|
2229
|
+
lens.innerHTML = '<div class="comty-color-magnifier-glass"><canvas width="9" height="9"></canvas><span></span></div>';
|
|
2230
|
+
root.appendChild(lens);
|
|
2231
|
+
const canvas = lens.querySelector('canvas');
|
|
2232
|
+
const context = canvas.getContext('2d', { willReadFrequently: true });
|
|
2233
|
+
let enabled = false, image = null, point = null, revision = 0, timer = 0;
|
|
2234
|
+
let sampledHex = null;
|
|
2235
|
+
|
|
2236
|
+
function paint() {
|
|
2237
|
+
if (!enabled || !point || !context) return;
|
|
2238
|
+
const { x, y, element } = point;
|
|
2239
|
+
if (!element.isConnected) { lens.hidden = true; return; }
|
|
2240
|
+
// An unavailable/unsafe page capture falls back to the element's CSS colour.
|
|
2241
|
+
let background = element;
|
|
2242
|
+
while (background.parentElement && getComputedStyle(background).backgroundColor === 'rgba(0, 0, 0, 0)') background = background.parentElement;
|
|
2243
|
+
const color = getComputedStyle(background).backgroundColor;
|
|
2244
|
+
context.clearRect(0, 0, 9, 9);
|
|
2245
|
+
context.fillStyle = getComputedStyle(root).getPropertyValue('--comty-paper');
|
|
2246
|
+
context.fillRect(0, 0, 9, 9);
|
|
2247
|
+
context.fillStyle = color;
|
|
2248
|
+
context.fillRect(0, 0, 9, 9);
|
|
2249
|
+
if (image) context.drawImage(image, Math.floor(x) - 4, Math.floor(y) - 4, 9, 9, 0, 0, 9, 9);
|
|
2250
|
+
lens.dataset.preview = image ? 'pixels' : 'color';
|
|
2251
|
+
sampledHex = null;
|
|
2252
|
+
try {
|
|
2253
|
+
const [r, g, b] = context.getImageData(4, 4, 1, 1).data;
|
|
2254
|
+
if (image) sampledHex = '#' + [r, g, b].map(channel => channel.toString(16).padStart(2, '0')).join('');
|
|
2255
|
+
lens.dataset.tone = .2126 * r + .7152 * g + .0722 * b < 128 ? 'dark' : 'light';
|
|
2256
|
+
} catch { lens.dataset.tone = 'light'; }
|
|
2257
|
+
lens.style.left = `${x}px`;
|
|
2258
|
+
lens.style.top = `${y}px`;
|
|
2259
|
+
lens.hidden = false;
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
async function refresh() {
|
|
2263
|
+
const version = ++revision;
|
|
2264
|
+
image = null;
|
|
2265
|
+
sampledHex = null;
|
|
2266
|
+
if (!enabled) return;
|
|
2267
|
+
try {
|
|
2268
|
+
const { blob } = await capture();
|
|
2269
|
+
if (!enabled || version !== revision) return;
|
|
2270
|
+
const dataUrl = await new Promise((resolve, reject) => {
|
|
2271
|
+
const reader = new FileReader();
|
|
2272
|
+
reader.onload = () => resolve(reader.result);
|
|
2273
|
+
reader.onerror = reject;
|
|
2274
|
+
reader.readAsDataURL(blob);
|
|
2275
|
+
});
|
|
2276
|
+
const next = new Image();
|
|
2277
|
+
next.src = dataUrl;
|
|
2278
|
+
await next.decode();
|
|
2279
|
+
if (!enabled || version !== revision) return;
|
|
2280
|
+
// Verify the bitmap can be sampled before using it for both preview and pick.
|
|
2281
|
+
const probe = document.createElement('canvas').getContext('2d');
|
|
2282
|
+
probe.drawImage(next, 0, 0);
|
|
2283
|
+
probe.getImageData(0, 0, 1, 1);
|
|
2284
|
+
image = next;
|
|
2285
|
+
paint();
|
|
2286
|
+
} catch { /* The CSS-colour fallback remains available without screen permission. */ }
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
function setEnabled(value) {
|
|
2290
|
+
if (enabled === value) return;
|
|
2291
|
+
enabled = value;
|
|
2292
|
+
lens.hidden = true;
|
|
2293
|
+
point = null;
|
|
2294
|
+
clearTimeout(timer);
|
|
2295
|
+
if (enabled) refresh();
|
|
2296
|
+
else { revision++; image = null; sampledHex = null; }
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
document.addEventListener('pointermove', event => {
|
|
2300
|
+
if (!enabled) return;
|
|
2301
|
+
if (!(event.target instanceof Element) || isComtyElement(event.target)) {
|
|
2302
|
+
point = null;
|
|
2303
|
+
sampledHex = null;
|
|
2304
|
+
lens.hidden = true;
|
|
2305
|
+
return;
|
|
2306
|
+
}
|
|
2307
|
+
point = { x: event.clientX, y: event.clientY, element: event.target };
|
|
2308
|
+
paint();
|
|
2309
|
+
}, true);
|
|
2310
|
+
document.documentElement.addEventListener('pointerleave', () => { point = null; lens.hidden = true; });
|
|
2311
|
+
function invalidate(event) {
|
|
2312
|
+
if (!enabled || event.target instanceof Element && isComtyElement(event.target)) return;
|
|
2313
|
+
revision++;
|
|
2314
|
+
image = null;
|
|
2315
|
+
sampledHex = null;
|
|
2316
|
+
point = null;
|
|
2317
|
+
lens.hidden = true;
|
|
2318
|
+
clearTimeout(timer);
|
|
2319
|
+
timer = setTimeout(refresh, 120);
|
|
2320
|
+
}
|
|
2321
|
+
document.addEventListener('scroll', invalidate, true);
|
|
2322
|
+
window.addEventListener('resize', invalidate);
|
|
2323
|
+
return { setEnabled, sample: () => sampledHex };
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2131
2326
|
// Comment thread UI and actions. The browser shell supplies the shared comment state.
|
|
2132
2327
|
function createCommentThread(context) {
|
|
2133
2328
|
const {
|
|
@@ -2631,6 +2826,216 @@ function createCommentThread(context) {
|
|
|
2631
2826
|
}
|
|
2632
2827
|
}
|
|
2633
2828
|
|
|
2829
|
+
const edgeAxis = { top: "y", bottom: "y", left: "x", right: "x" };
|
|
2830
|
+
const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
2831
|
+
|
|
2832
|
+
/** Pick a nearby border segment, including when the pointer is just outside it. */
|
|
2833
|
+
function nearestMeasurementEdge(candidates, point, { axis, exclude, tolerance = 10 } = {}) {
|
|
2834
|
+
let nearest = null;
|
|
2835
|
+
let distance = tolerance;
|
|
2836
|
+
for (const { element, rect } of candidates) {
|
|
2837
|
+
if (rect.width <= 0 || rect.height <= 0) continue;
|
|
2838
|
+
for (const edge of ["top", "bottom", "left", "right"]) {
|
|
2839
|
+
if (axis && edgeAxis[edge] !== axis) continue;
|
|
2840
|
+
if (exclude?.element === element && exclude.edge === edge) continue;
|
|
2841
|
+
const vertical = edgeAxis[edge] === "y";
|
|
2842
|
+
const x = vertical ? clamp(point.x, rect.left, rect.right) : rect[edge];
|
|
2843
|
+
const y = vertical ? rect[edge] : clamp(point.y, rect.top, rect.bottom);
|
|
2844
|
+
const nextDistance = Math.hypot(point.x - x, point.y - y);
|
|
2845
|
+
if (nextDistance > distance || (nearest && nextDistance === distance)) continue;
|
|
2846
|
+
distance = nextDistance;
|
|
2847
|
+
nearest = { element, edge, axis: edgeAxis[edge], offset: vertical ? (x - rect.left) / rect.width : (y - rect.top) / rect.height };
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
return nearest;
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
function measurementPoint(anchor, rect, scroll = { x: 0, y: 0 }) {
|
|
2854
|
+
if (!anchor.element) return { x: anchor.x - scroll.x, y: anchor.y - scroll.y };
|
|
2855
|
+
return anchor.axis === "y"
|
|
2856
|
+
? { x: rect.left + rect.width * anchor.offset, y: rect[anchor.edge] }
|
|
2857
|
+
: { x: rect[anchor.edge], y: rect.top + rect.height * anchor.offset };
|
|
2858
|
+
}
|
|
2859
|
+
|
|
2860
|
+
/** Border measurements live for one activation of the existing ruler tool. */
|
|
2861
|
+
function createMeasurements({ root, isComtyElement, isElementTreeHidden, onExit }) {
|
|
2862
|
+
let enabled = false;
|
|
2863
|
+
let hover = null;
|
|
2864
|
+
let drag = null;
|
|
2865
|
+
let pointer = null;
|
|
2866
|
+
let signature = "";
|
|
2867
|
+
const measurements = [];
|
|
2868
|
+
const svgNS = "http://www.w3.org/2000/svg";
|
|
2869
|
+
const layer = document.createElementNS(svgNS, "svg");
|
|
2870
|
+
layer.classList.add("comty-distance-layer");
|
|
2871
|
+
layer.setAttribute("aria-hidden", "true");
|
|
2872
|
+
root.prepend(layer);
|
|
2873
|
+
const announcement = document.createElement("span");
|
|
2874
|
+
announcement.className = "comty-distance-announcement";
|
|
2875
|
+
announcement.setAttribute("role", "status");
|
|
2876
|
+
root.appendChild(announcement);
|
|
2877
|
+
|
|
2878
|
+
function rectOf(element) {
|
|
2879
|
+
if (element === document.documentElement) {
|
|
2880
|
+
const width = Math.max(element.clientWidth, element.scrollWidth);
|
|
2881
|
+
const height = Math.max(element.clientHeight, element.scrollHeight);
|
|
2882
|
+
return { left: -scrollX, top: -scrollY, right: width - scrollX, bottom: height - scrollY, width, height };
|
|
2883
|
+
}
|
|
2884
|
+
return element.getBoundingClientRect();
|
|
2885
|
+
}
|
|
2886
|
+
function pointOf(anchor) {
|
|
2887
|
+
return measurementPoint(anchor, anchor.element && rectOf(anchor.element), { x: scrollX, y: scrollY });
|
|
2888
|
+
}
|
|
2889
|
+
function candidatesAt(point) {
|
|
2890
|
+
const elements = new Set();
|
|
2891
|
+
// Sample around the cursor as well as underneath it to catch an outside edge.
|
|
2892
|
+
for (const [dx, dy] of [[0, 0], [-10, 0], [10, 0], [0, -10], [0, 10]]) {
|
|
2893
|
+
for (const hit of document.elementsFromPoint(clamp(point.x + dx, 0, innerWidth - 1), clamp(point.y + dy, 0, innerHeight - 1))) {
|
|
2894
|
+
if (isComtyElement(hit)) continue;
|
|
2895
|
+
for (let element = hit; element; element = element.parentElement) elements.add(element);
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
return [...elements].filter(element => !isElementTreeHidden(element)).map(element => ({ element, rect: rectOf(element) }));
|
|
2899
|
+
}
|
|
2900
|
+
function findEdge(point, start) {
|
|
2901
|
+
return nearestMeasurementEdge(candidatesAt(point), point, { axis: start?.axis, exclude: start });
|
|
2902
|
+
}
|
|
2903
|
+
function setCursor(axis) {
|
|
2904
|
+
document.documentElement.classList.toggle("comty-measure-vertical", axis === "y");
|
|
2905
|
+
document.documentElement.classList.toggle("comty-measure-horizontal", axis === "x");
|
|
2906
|
+
}
|
|
2907
|
+
function updatePointer(event) {
|
|
2908
|
+
pointer = { x: event.clientX, y: event.clientY };
|
|
2909
|
+
hover = findEdge(pointer, drag?.start);
|
|
2910
|
+
if (drag) {
|
|
2911
|
+
drag.end = hover || { x: pointer.x + scrollX, y: pointer.y + scrollY };
|
|
2912
|
+
// Keep the two anchors perpendicular when both borders share that coordinate.
|
|
2913
|
+
if (hover) {
|
|
2914
|
+
const start = pointOf(drag.start), rect = rectOf(hover.element);
|
|
2915
|
+
drag.end = { ...hover, offset: drag.start.axis === "y"
|
|
2916
|
+
? clamp((start.x - rect.left) / rect.width, 0, 1)
|
|
2917
|
+
: clamp((start.y - rect.top) / rect.height, 0, 1) };
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
setCursor(drag?.start.axis || hover?.axis);
|
|
2921
|
+
render();
|
|
2922
|
+
}
|
|
2923
|
+
function node(tag, attributes, parent = layer) {
|
|
2924
|
+
const element = document.createElementNS(svgNS, tag);
|
|
2925
|
+
for (const [key, value] of Object.entries(attributes)) element.setAttribute(key, value);
|
|
2926
|
+
parent.appendChild(element);
|
|
2927
|
+
return element;
|
|
2928
|
+
}
|
|
2929
|
+
function valid(anchor) {
|
|
2930
|
+
return !anchor.element || (anchor.element.isConnected && !isElementTreeHidden(anchor.element));
|
|
2931
|
+
}
|
|
2932
|
+
function render() {
|
|
2933
|
+
if (!enabled) return;
|
|
2934
|
+
const visible = [...measurements, ...(drag ? [drag] : [])].filter(item => valid(item.start) && valid(item.end));
|
|
2935
|
+
const geometry = visible.map(item => ({ start: pointOf(item.start), end: pointOf(item.end), axis: item.start.axis }));
|
|
2936
|
+
const hoverPoint = hover && valid(hover) ? pointOf(hover) : null;
|
|
2937
|
+
const next = JSON.stringify([geometry, hoverPoint]);
|
|
2938
|
+
if (signature === next) return;
|
|
2939
|
+
signature = next;
|
|
2940
|
+
layer.replaceChildren();
|
|
2941
|
+
for (const { start, end, axis } of geometry) {
|
|
2942
|
+
const vertical = axis === "y";
|
|
2943
|
+
const distance = Math.round(Math.abs(end[axis] - start[axis]));
|
|
2944
|
+
if (distance < 1) continue;
|
|
2945
|
+
const x2 = vertical ? start.x : end.x, y2 = vertical ? end.y : start.y;
|
|
2946
|
+
const group = node("g", { "data-comty-distance": distance });
|
|
2947
|
+
node("path", { d: `M${start.x} ${start.y}L${x2} ${y2}`, class: "comty-distance-line" }, group);
|
|
2948
|
+
if (Math.hypot(end.x - x2, end.y - y2) > 1) node("path", { d: `M${x2} ${y2}L${end.x} ${end.y}`, class: "comty-distance-extension" }, group);
|
|
2949
|
+
const direction = Math.sign(end[axis] - start[axis]);
|
|
2950
|
+
for (const [x, y, inward] of [[start.x, start.y, direction], [x2, y2, -direction]]) {
|
|
2951
|
+
const points = vertical ? `${x},${y} ${x - 3},${y + inward * 6} ${x + 3},${y + inward * 6}` : `${x},${y} ${x + inward * 6},${y - 3} ${x + inward * 6},${y + 3}`;
|
|
2952
|
+
node("polygon", { points, class: "comty-distance-arrow" }, group);
|
|
2953
|
+
}
|
|
2954
|
+
const text = String(distance), width = text.length * 7.5 + 12;
|
|
2955
|
+
const labelX = vertical ? start.x - width - 8 : (start.x + x2 - width) / 2;
|
|
2956
|
+
const labelY = vertical ? (start.y + y2) / 2 - 10 : start.y - 28;
|
|
2957
|
+
const label = node("g", { transform: `translate(${clamp(labelX, 2, innerWidth - width - 2)} ${clamp(labelY, 2, innerHeight - 22)})` }, group);
|
|
2958
|
+
node("rect", { width, height: 20, rx: 3, class: "comty-distance-label" }, label);
|
|
2959
|
+
node("text", { x: width / 2, y: 14, "text-anchor": "middle", class: "comty-distance-value" }, label).textContent = text;
|
|
2960
|
+
}
|
|
2961
|
+
if (hoverPoint) node("circle", { cx: hoverPoint.x, cy: hoverPoint.y, r: 4, class: "comty-distance-snap" });
|
|
2962
|
+
}
|
|
2963
|
+
function cancel() {
|
|
2964
|
+
const pointerId = drag?.pointerId;
|
|
2965
|
+
drag = null;
|
|
2966
|
+
if (pointerId !== undefined && document.documentElement.hasPointerCapture(pointerId)) document.documentElement.releasePointerCapture(pointerId);
|
|
2967
|
+
hover = null;
|
|
2968
|
+
pointer = null;
|
|
2969
|
+
setCursor(null);
|
|
2970
|
+
render();
|
|
2971
|
+
}
|
|
2972
|
+
function setEnabled(value) {
|
|
2973
|
+
if (enabled === value) return;
|
|
2974
|
+
enabled = value;
|
|
2975
|
+
layer.style.display = value ? "" : "none";
|
|
2976
|
+
if (!value) {
|
|
2977
|
+
measurements.length = 0;
|
|
2978
|
+
cancel();
|
|
2979
|
+
layer.replaceChildren();
|
|
2980
|
+
announcement.textContent = "";
|
|
2981
|
+
signature = "";
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
function isHostEvent(event) {
|
|
2985
|
+
if (!enabled || !(event.target instanceof Element)) return false;
|
|
2986
|
+
if (!isComtyElement(event.target)) return true;
|
|
2987
|
+
// The outer ruler strip must still let us anchor to the page's top/left edge.
|
|
2988
|
+
return event.target.matches(".comty-ruler") && (event.clientX + scrollX <= 10 || event.clientY + scrollY <= 10);
|
|
2989
|
+
}
|
|
2990
|
+
document.addEventListener("pointermove", event => {
|
|
2991
|
+
if (!enabled) return;
|
|
2992
|
+
if (drag && event.pointerId !== drag.pointerId) return;
|
|
2993
|
+
if (!isHostEvent(event) && !drag) { hover = null; setCursor(null); render(); return; }
|
|
2994
|
+
event.stopImmediatePropagation();
|
|
2995
|
+
updatePointer(event);
|
|
2996
|
+
}, true);
|
|
2997
|
+
document.addEventListener("pointerdown", event => {
|
|
2998
|
+
if (!isHostEvent(event) || event.button !== 0) return;
|
|
2999
|
+
event.preventDefault(); event.stopImmediatePropagation();
|
|
3000
|
+
updatePointer(event);
|
|
3001
|
+
if (hover) {
|
|
3002
|
+
drag = { start: hover, end: hover, pointerId: event.pointerId };
|
|
3003
|
+
if (event.isTrusted) document.documentElement.setPointerCapture(event.pointerId);
|
|
3004
|
+
}
|
|
3005
|
+
}, true);
|
|
3006
|
+
document.addEventListener("pointerup", event => {
|
|
3007
|
+
if (!drag || event.pointerId !== drag.pointerId) return;
|
|
3008
|
+
event.preventDefault(); event.stopImmediatePropagation();
|
|
3009
|
+
updatePointer(event);
|
|
3010
|
+
const distance = Math.round(Math.abs(pointOf(drag.end)[drag.start.axis] - pointOf(drag.start)[drag.start.axis]));
|
|
3011
|
+
if (distance > 0) {
|
|
3012
|
+
measurements.push(drag);
|
|
3013
|
+
announcement.textContent = `${distance} pixels ${drag.start.axis === "y" ? "vertically" : "horizontally"}`;
|
|
3014
|
+
}
|
|
3015
|
+
drag = null;
|
|
3016
|
+
if (document.documentElement.hasPointerCapture(event.pointerId)) document.documentElement.releasePointerCapture(event.pointerId);
|
|
3017
|
+
render();
|
|
3018
|
+
}, true);
|
|
3019
|
+
document.addEventListener("click", event => {
|
|
3020
|
+
if (isHostEvent(event)) { event.preventDefault(); event.stopImmediatePropagation(); }
|
|
3021
|
+
}, true);
|
|
3022
|
+
document.addEventListener("keydown", event => {
|
|
3023
|
+
if (!enabled || event.key !== "Escape" || event.target.closest?.(".comty-auth-dialog, input, textarea, [contenteditable=true]")) return;
|
|
3024
|
+
event.preventDefault(); event.stopImmediatePropagation();
|
|
3025
|
+
if (drag) cancel(); else onExit();
|
|
3026
|
+
}, true);
|
|
3027
|
+
document.addEventListener("pointercancel", cancel, true);
|
|
3028
|
+
document.documentElement.addEventListener("lostpointercapture", event => { if (drag?.pointerId === event.pointerId) cancel(); });
|
|
3029
|
+
document.documentElement.addEventListener("pointerleave", () => { if (!drag) cancel(); });
|
|
3030
|
+
window.addEventListener("blur", cancel);
|
|
3031
|
+
document.addEventListener("scroll", () => {
|
|
3032
|
+
if (!enabled) return;
|
|
3033
|
+
if (pointer) updatePointer({ clientX: pointer.x, clientY: pointer.y });
|
|
3034
|
+
else render();
|
|
3035
|
+
}, { capture: true, passive: true });
|
|
3036
|
+
return { setEnabled, render };
|
|
3037
|
+
}
|
|
3038
|
+
|
|
2634
3039
|
// Shared browser runtime. Both npm and the standalone CDN script use this file.
|
|
2635
3040
|
const instanceKey = Symbol.for("comty.sdk.instance");
|
|
2636
3041
|
|
|
@@ -2768,6 +3173,13 @@ function initComty(options = {}) {
|
|
|
2768
3173
|
const style = document.createElement("style");
|
|
2769
3174
|
style.innerHTML = getComtyStyle();
|
|
2770
3175
|
document.head.appendChild(style);
|
|
3176
|
+
if (!document.querySelector('link[data-comty-font]')) {
|
|
3177
|
+
const font = document.createElement('link');
|
|
3178
|
+
font.rel = 'stylesheet';
|
|
3179
|
+
font.href = 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap';
|
|
3180
|
+
font.dataset.comtyFont = '';
|
|
3181
|
+
document.head.appendChild(font);
|
|
3182
|
+
}
|
|
2771
3183
|
|
|
2772
3184
|
const toolbar = document.createElement("div");
|
|
2773
3185
|
toolbar.className = "comty-toolbar";
|
|
@@ -2781,6 +3193,12 @@ function initComty(options = {}) {
|
|
|
2781
3193
|
toolbarSurface.className = "comty-toolbar-surface";
|
|
2782
3194
|
toolbar.appendChild(toolbarSurface);
|
|
2783
3195
|
const icon = (paths) => `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${paths}</svg>`;
|
|
3196
|
+
const dragHandle = document.createElement("span");
|
|
3197
|
+
dragHandle.className = "comty-toolbar-grip";
|
|
3198
|
+
dragHandle.title = "Drag to move";
|
|
3199
|
+
dragHandle.setAttribute("aria-hidden", "true");
|
|
3200
|
+
dragHandle.innerHTML = icon('<g fill="currentColor" stroke="none"><circle cx="9" cy="6" r="1.5"/><circle cx="15" cy="6" r="1.5"/><circle cx="9" cy="12" r="1.5"/><circle cx="15" cy="12" r="1.5"/><circle cx="9" cy="18" r="1.5"/><circle cx="15" cy="18" r="1.5"/></g>');
|
|
3201
|
+
toolbar.appendChild(dragHandle);
|
|
2784
3202
|
function toolButton(label, paths, action) {
|
|
2785
3203
|
const button = document.createElement("button");
|
|
2786
3204
|
button.className = "comty-control-button";
|
|
@@ -2828,6 +3246,10 @@ function initComty(options = {}) {
|
|
|
2828
3246
|
agentHistoryBtn.setAttribute("aria-expanded", "false");
|
|
2829
3247
|
agentTools.appendChild(agentHistoryBtn);
|
|
2830
3248
|
}
|
|
3249
|
+
const primaryTools = document.createElement("div");
|
|
3250
|
+
primaryTools.className = "comty-tool-primary";
|
|
3251
|
+
toolbar.insertBefore(primaryTools, browseBtn);
|
|
3252
|
+
for (const button of [browseBtn, inspectorBtn, btn, agentBtn].filter(Boolean)) primaryTools.appendChild(button);
|
|
2831
3253
|
const inspectorTools = document.createElement("div");
|
|
2832
3254
|
inspectorTools.className = "comty-tool-context";
|
|
2833
3255
|
inspectorTools.hidden = true;
|
|
@@ -2841,11 +3263,24 @@ function initComty(options = {}) {
|
|
|
2841
3263
|
let rulerDrag = null;
|
|
2842
3264
|
const rulerGuides = [];
|
|
2843
3265
|
let colorPickerController = null;
|
|
2844
|
-
|
|
2845
|
-
|
|
3266
|
+
const recentColors = [];
|
|
3267
|
+
const recentFonts = [];
|
|
3268
|
+
let propertyKind = null;
|
|
3269
|
+
let propertySelection = null;
|
|
3270
|
+
let propertyOpener = null;
|
|
3271
|
+
let propertyAnimation = null;
|
|
3272
|
+
let propertyStripAnimation = null;
|
|
3273
|
+
let propertyPicking = null;
|
|
3274
|
+
const colorMagnifier = createColorMagnifier({ root, isComtyElement, capture: () => captureViewportScreenshot() });
|
|
2846
3275
|
const overlayTools = document.createElement("div");
|
|
2847
3276
|
overlayTools.className = "comty-overlay-tools";
|
|
2848
3277
|
root.prepend(overlayTools);
|
|
3278
|
+
const measurements = createMeasurements({
|
|
3279
|
+
root,
|
|
3280
|
+
isComtyElement,
|
|
3281
|
+
isElementTreeHidden,
|
|
3282
|
+
onExit() { rulersEnabled = false; syncInspectorTools(); },
|
|
3283
|
+
});
|
|
2849
3284
|
const gridLayer = document.createElement("div");
|
|
2850
3285
|
gridLayer.className = "comty-layout-guides";
|
|
2851
3286
|
overlayTools.appendChild(gridLayer);
|
|
@@ -2857,6 +3292,11 @@ function initComty(options = {}) {
|
|
|
2857
3292
|
toolPanel.className = "comty-tool-panel";
|
|
2858
3293
|
toolPanel.hidden = true;
|
|
2859
3294
|
root.appendChild(toolPanel);
|
|
3295
|
+
const propertyPanel = document.createElement("section");
|
|
3296
|
+
propertyPanel.className = "comty-property-panel";
|
|
3297
|
+
propertyPanel.id = "comty-property-panel";
|
|
3298
|
+
propertyPanel.hidden = true;
|
|
3299
|
+
root.appendChild(propertyPanel);
|
|
2860
3300
|
const xrayStyle = document.createElement("style");
|
|
2861
3301
|
// Use an outline overlay: geometry and the page's existing styles stay intact.
|
|
2862
3302
|
xrayStyle.textContent = `body *:not(#comty-root):not(#comty-root *) { outline: 1px solid ${getComputedStyle(root).getPropertyValue('--comty-xray').trim()} !important; }`;
|
|
@@ -2865,10 +3305,17 @@ function initComty(options = {}) {
|
|
|
2865
3305
|
syncInspectorTools();
|
|
2866
3306
|
});
|
|
2867
3307
|
const rulersBtn = toolButton("Rulers · R", '<path d="M3 3h18v6H9v12H3Z"/><path d="M7 3v3m4-3v3m4-3v3M3 11h3m-3 4h3m-3 4h3"/>', () => {
|
|
3308
|
+
closePropertyPanel();
|
|
3309
|
+
if (motion.enabled) setMotionEnabled(false);
|
|
2868
3310
|
rulersEnabled = !rulersEnabled;
|
|
3311
|
+
selectedElement = null;
|
|
3312
|
+
measuredRegion = null;
|
|
3313
|
+
inspectDrag = null;
|
|
3314
|
+
hideInspector();
|
|
2869
3315
|
syncInspectorTools();
|
|
2870
3316
|
});
|
|
2871
3317
|
const gridBtn = toolButton("Layout guides · G", '<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 10h18M10 3v18"/>', () => {
|
|
3318
|
+
closePropertyPanel();
|
|
2872
3319
|
if (motion.enabled) setMotionEnabled(false);
|
|
2873
3320
|
gridPanelOpen = !gridPanelOpen;
|
|
2874
3321
|
if (gridPanelOpen && !layoutGuides.length) addLayoutGuide();
|
|
@@ -2876,10 +3323,16 @@ function initComty(options = {}) {
|
|
|
2876
3323
|
syncInspectorTools();
|
|
2877
3324
|
});
|
|
2878
3325
|
gridBtn.setAttribute("aria-expanded", "false");
|
|
2879
|
-
const pipetteBtn = toolButton("Sample color · P", '<path d="m16 3 5 5-4 4-5-5Zm-3 5L4 17v3h3l9-9M11 6l7 7"/>',
|
|
3326
|
+
const pipetteBtn = toolButton("Sample color · P", '<path d="m16 3 5 5-4 4-5-5Zm-3 5L4 17v3h3l9-9M11 6l7 7"/>', event => togglePropertyPanel("color", pipetteBtn, event.shiftKey));
|
|
3327
|
+
pipetteBtn.title = "Sample color · P · Shift-click to sample the screen";
|
|
3328
|
+
const typographyBtn = toolButton("Typography · T", '<path d="m5 20 7-16 7 16M8 14h8M3 20h5m8 0h5M10 4h4"/>', () => togglePropertyPanel("font", typographyBtn));
|
|
3329
|
+
for (const button of [pipetteBtn, typographyBtn]) {
|
|
3330
|
+
button.setAttribute("aria-controls", propertyPanel.id);
|
|
3331
|
+
button.setAttribute("aria-expanded", "false");
|
|
3332
|
+
}
|
|
2880
3333
|
const motionBtn = toolButton("Motion · M", '<circle cx="12" cy="14" r="8"/><path d="M9 2h6m-3 4V2m6 5 2-2m-8 5v4l3 2"/>', () => setMotionEnabled(!motion.enabled));
|
|
2881
3334
|
motionBtn.setAttribute("aria-expanded", "false");
|
|
2882
|
-
for (const button of [xrayBtn, rulersBtn, gridBtn, pipetteBtn, motionBtn]) inspectorTools.appendChild(button);
|
|
3335
|
+
for (const button of [xrayBtn, rulersBtn, gridBtn, pipetteBtn, typographyBtn, motionBtn]) inspectorTools.appendChild(button);
|
|
2883
3336
|
|
|
2884
3337
|
const motion = { enabled: false, speed: 1, paused: false, managed: new Map(), shadowRoots: [], nextRootScan: 0 };
|
|
2885
3338
|
const motionPanel = document.createElement('section');
|
|
@@ -2928,6 +3381,9 @@ function initComty(options = {}) {
|
|
|
2928
3381
|
motion.enabled = enabled; motionPanel.hidden = !enabled;
|
|
2929
3382
|
motionBtn.setAttribute('aria-pressed', String(enabled)); motionBtn.setAttribute('aria-expanded', String(enabled));
|
|
2930
3383
|
if (enabled) {
|
|
3384
|
+
closePropertyPanel();
|
|
3385
|
+
rulersEnabled = false;
|
|
3386
|
+
syncInspectorTools();
|
|
2931
3387
|
gridPanelOpen = false; toolPanel.hidden = true; gridBtn.setAttribute('aria-expanded', 'false');
|
|
2932
3388
|
measuredRegion = null; inspectDrag = null; selectedElement = null; hideInspector();
|
|
2933
3389
|
motion.nextRootScan = 0; updateMotion(); renderMotionPanel(); positionToolbar(); ensureLiveTracking();
|
|
@@ -3031,32 +3487,101 @@ function initComty(options = {}) {
|
|
|
3031
3487
|
}
|
|
3032
3488
|
}
|
|
3033
3489
|
|
|
3490
|
+
let closeGuideMenu = () => {};
|
|
3491
|
+
function guideSelect(label, options, value, onChange) {
|
|
3492
|
+
const trigger = document.createElement("button");
|
|
3493
|
+
trigger.type = "button";
|
|
3494
|
+
trigger.className = "comty-guide-select";
|
|
3495
|
+
trigger.setAttribute("aria-label", label);
|
|
3496
|
+
trigger.setAttribute("aria-haspopup", "menu");
|
|
3497
|
+
trigger.setAttribute("aria-expanded", "false");
|
|
3498
|
+
function setValue(next) {
|
|
3499
|
+
trigger.value = next;
|
|
3500
|
+
trigger.innerHTML = `${options.find(option => option[0] === next)[1]}${icon('<path d="m8 10 4 4 4-4"/>')}`;
|
|
3501
|
+
}
|
|
3502
|
+
setValue(value);
|
|
3503
|
+
function open() {
|
|
3504
|
+
const wasOpen = trigger.getAttribute("aria-expanded") === "true";
|
|
3505
|
+
closeGuideMenu();
|
|
3506
|
+
if (wasOpen) return;
|
|
3507
|
+
root.querySelectorAll('.comty-popover').forEach(popover => popover.remove());
|
|
3508
|
+
const menu = document.createElement("div");
|
|
3509
|
+
menu.className = "comty-popover comty-guide-popover";
|
|
3510
|
+
menu.setAttribute("role", "menu");
|
|
3511
|
+
menu.setAttribute("aria-label", label);
|
|
3512
|
+
const controller = new AbortController();
|
|
3513
|
+
const close = (focus = false) => {
|
|
3514
|
+
controller.abort(); menu.remove();
|
|
3515
|
+
trigger.setAttribute("aria-expanded", "false");
|
|
3516
|
+
if (focus && trigger.isConnected) trigger.focus();
|
|
3517
|
+
closeGuideMenu = () => {};
|
|
3518
|
+
};
|
|
3519
|
+
closeGuideMenu = close;
|
|
3520
|
+
const items = options.map(([key, name]) => {
|
|
3521
|
+
const item = document.createElement("button");
|
|
3522
|
+
item.type = "button";
|
|
3523
|
+
item.className = "comty-popover-item";
|
|
3524
|
+
item.setAttribute("role", "menuitemradio");
|
|
3525
|
+
item.setAttribute("aria-checked", String(key === trigger.value));
|
|
3526
|
+
item.textContent = name;
|
|
3527
|
+
if (key === trigger.value) {
|
|
3528
|
+
const check = document.createElement("span");
|
|
3529
|
+
check.className = "comty-popover-item-check";
|
|
3530
|
+
check.innerHTML = icon('<path d="m5 12 4 4 10-10"/>');
|
|
3531
|
+
item.appendChild(check);
|
|
3532
|
+
}
|
|
3533
|
+
item.onclick = () => { close(); setValue(key); onChange(key); if (trigger.isConnected) trigger.focus(); };
|
|
3534
|
+
menu.appendChild(item);
|
|
3535
|
+
return item;
|
|
3536
|
+
});
|
|
3537
|
+
toolPanel.appendChild(menu);
|
|
3538
|
+
trigger.setAttribute("aria-expanded", "true");
|
|
3539
|
+
const rect = trigger.getBoundingClientRect();
|
|
3540
|
+
menu.style.left = `${Math.max(8, Math.min(rect.right - menu.offsetWidth, innerWidth - menu.offsetWidth - 8))}px`;
|
|
3541
|
+
menu.style.top = `${rect.bottom + menu.offsetHeight + 4 <= innerHeight - 8 ? rect.bottom + 4 : Math.max(8, rect.top - menu.offsetHeight - 4)}px`;
|
|
3542
|
+
items[options.findIndex(option => option[0] === trigger.value)].focus();
|
|
3543
|
+
menu.onkeydown = event => {
|
|
3544
|
+
const index = items.indexOf(document.activeElement);
|
|
3545
|
+
if (["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) {
|
|
3546
|
+
event.preventDefault(); event.stopPropagation();
|
|
3547
|
+
items[event.key === "Home" ? 0 : event.key === "End" ? items.length - 1 : (index + (event.key === "ArrowDown" ? 1 : -1) + items.length) % items.length].focus();
|
|
3548
|
+
} else if (event.key === "Escape") { event.preventDefault(); event.stopPropagation(); close(true); }
|
|
3549
|
+
else if (event.key === "Tab") close();
|
|
3550
|
+
};
|
|
3551
|
+
document.addEventListener("pointerdown", event => {
|
|
3552
|
+
if (!menu.contains(event.target) && !trigger.contains(event.target)) close();
|
|
3553
|
+
}, {capture: true, signal: controller.signal});
|
|
3554
|
+
window.addEventListener("resize", () => close(), {signal: controller.signal});
|
|
3555
|
+
}
|
|
3556
|
+
trigger.onclick = open;
|
|
3557
|
+
trigger.onkeydown = event => {
|
|
3558
|
+
if (["ArrowDown", "ArrowUp"].includes(event.key)) { event.preventDefault(); open(); }
|
|
3559
|
+
};
|
|
3560
|
+
return { trigger, setValue };
|
|
3561
|
+
}
|
|
3562
|
+
|
|
3034
3563
|
function renderGridPanel() {
|
|
3035
|
-
|
|
3036
|
-
colorToastAnimation?.cancel();
|
|
3037
|
-
colorToastAnimation = null;
|
|
3038
|
-
toolPanel.classList.remove("comty-color-toast");
|
|
3564
|
+
closeGuideMenu();
|
|
3039
3565
|
toolPanel.hidden = !gridPanelOpen || !session.inspect;
|
|
3040
3566
|
gridBtn.setAttribute("aria-expanded", String(gridPanelOpen && session.inspect));
|
|
3041
3567
|
if (toolPanel.hidden) return;
|
|
3042
3568
|
toolPanel.setAttribute("aria-label", "Layout guides");
|
|
3043
|
-
toolPanel.innerHTML = `<div class="comty-tool-panel-header"><strong>Layout guides</strong><
|
|
3569
|
+
toolPanel.innerHTML = `<div class="comty-tool-panel-header"><strong>Layout guides</strong><span data-guide-type></span><button type="button" data-add aria-label="Add layout guide">${icon('<path d="M12 5v14M5 12h14"/>')}</button></div><div class="comty-guide-list"></div>`;
|
|
3044
3570
|
const selectedGuide = layoutGuides.find(guide => guide.id === selectedGuideId) || layoutGuides[0];
|
|
3045
3571
|
selectedGuideId = selectedGuide?.id ?? null;
|
|
3046
|
-
const typeSelect =
|
|
3047
|
-
typeSelect.value = selectedGuide?.kind ?? "columns";
|
|
3048
|
-
typeSelect.onchange = () => {
|
|
3572
|
+
const typeSelect = guideSelect("Guide type", [["columns", "Columns"], ["rows", "Rows"], ["grid", "Grid"]], selectedGuide?.kind ?? "columns", value => {
|
|
3049
3573
|
const guide = layoutGuides.find(guide => guide.id === selectedGuideId);
|
|
3050
3574
|
if (guide) {
|
|
3051
|
-
if ((guide.kind === "grid") !== (
|
|
3052
|
-
guide.kind =
|
|
3053
|
-
} else addLayoutGuide(
|
|
3575
|
+
if ((guide.kind === "grid") !== (value === "grid")) guide.size = value === "grid" ? 8 : 72;
|
|
3576
|
+
guide.kind = value;
|
|
3577
|
+
} else addLayoutGuide(value);
|
|
3054
3578
|
renderGridPanel();
|
|
3055
3579
|
syncInspectorTools();
|
|
3056
|
-
toolPanel.querySelector("
|
|
3057
|
-
};
|
|
3580
|
+
toolPanel.querySelector('[aria-label="Guide type"]').focus();
|
|
3581
|
+
});
|
|
3582
|
+
toolPanel.querySelector("[data-guide-type]").replaceWith(typeSelect.trigger);
|
|
3058
3583
|
toolPanel.querySelector("[data-add]").onclick = () => {
|
|
3059
|
-
addLayoutGuide(
|
|
3584
|
+
addLayoutGuide(typeSelect.trigger.value);
|
|
3060
3585
|
renderGridPanel();
|
|
3061
3586
|
};
|
|
3062
3587
|
const list = toolPanel.querySelector(".comty-guide-list");
|
|
@@ -3069,8 +3594,14 @@ function initComty(options = {}) {
|
|
|
3069
3594
|
item.innerHTML = `<div class="comty-guide-row"><button type="button" data-edit aria-expanded="false">${escapeInspectorText(name)} <span aria-hidden="true">›</span></button><button type="button" data-visible aria-label="Show ${guide.kind}" aria-pressed="${guide.visible}">${icon('<path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12Z"/><circle cx="12" cy="12" r="3"/>')}</button><button type="button" data-remove aria-label="Remove ${guide.kind}">${icon('<path d="M5 12h14"/>')}</button></div><div class="comty-guide-fields" hidden></div>`;
|
|
3070
3595
|
const fields = item.querySelector(".comty-guide-fields");
|
|
3071
3596
|
const numberField = (label, key, min, max) => `<label>${label}<input type="number" data-field="${key}" value="${guide[key]}" min="${min}" max="${max}" step="1"></label>`;
|
|
3072
|
-
fields.innerHTML = `${guide.kind !== "grid" ? numberField("Count", "count", 1, 24) : ""}<label>Color<input type="color" data-field="color" value="${guide.color}"></label>${numberField("Opacity %", "opacity", 0, 100)}${guide.kind !== "grid" ? `<
|
|
3073
|
-
if (guide.kind !== "grid")
|
|
3597
|
+
fields.innerHTML = `${guide.kind !== "grid" ? numberField("Count", "count", 1, 24) : ""}<label>Color<input type="color" data-field="color" value="${guide.color}"></label>${numberField("Opacity %", "opacity", 0, 100)}${guide.kind !== "grid" ? `<div class="comty-guide-align"><span>Type</span><span data-guide-align></span></div>` : ""}${numberField(guide.kind === "grid" ? "Size" : guide.kind === "rows" ? "Height" : "Width", "size", 1, 4096)}${numberField("Offset", "offset", 0, 4096)}${guide.kind !== "grid" ? numberField("Gutter", "gutter", 0, 800) : ""}`;
|
|
3598
|
+
if (guide.kind !== "grid") {
|
|
3599
|
+
const alignment = guideSelect("Guide alignment", [["stretch", "Stretch"], ["start", "Start"], ["center", "Center"], ["end", "End"]], guide.align, value => {
|
|
3600
|
+
guide.align = value;
|
|
3601
|
+
renderLayoutGuides();
|
|
3602
|
+
});
|
|
3603
|
+
fields.querySelector("[data-guide-align]").replaceWith(alignment.trigger);
|
|
3604
|
+
}
|
|
3074
3605
|
fields.oninput = (event) => {
|
|
3075
3606
|
const input = event.target, key = input.dataset.field;
|
|
3076
3607
|
if (!key) return;
|
|
@@ -3086,7 +3617,7 @@ function initComty(options = {}) {
|
|
|
3086
3617
|
};
|
|
3087
3618
|
item.querySelector("[data-edit]").onclick = (event) => {
|
|
3088
3619
|
selectedGuideId = guide.id;
|
|
3089
|
-
typeSelect.
|
|
3620
|
+
typeSelect.setValue(guide.kind);
|
|
3090
3621
|
fields.hidden = !fields.hidden;
|
|
3091
3622
|
event.currentTarget.setAttribute("aria-expanded", String(!fields.hidden));
|
|
3092
3623
|
};
|
|
@@ -3106,12 +3637,14 @@ function initComty(options = {}) {
|
|
|
3106
3637
|
|
|
3107
3638
|
function syncInspectorTools() {
|
|
3108
3639
|
const visible = session.inspect;
|
|
3640
|
+
measurements.setEnabled(visible && rulersEnabled && !motion.enabled);
|
|
3109
3641
|
overlayTools.hidden = !visible;
|
|
3110
3642
|
xrayBtn.setAttribute("aria-pressed", String(visible && xrayEnabled));
|
|
3111
3643
|
rulersBtn.setAttribute("aria-pressed", String(visible && rulersEnabled));
|
|
3112
3644
|
gridBtn.setAttribute("aria-pressed", String(visible && layoutGuides.some(guide => guide.visible)));
|
|
3113
3645
|
gridBtn.setAttribute("aria-expanded", String(visible && gridPanelOpen));
|
|
3114
3646
|
toolPanel.hidden = !visible || !gridPanelOpen;
|
|
3647
|
+
if (toolPanel.hidden) closeGuideMenu();
|
|
3115
3648
|
rulerLayer.hidden = !visible || !rulersEnabled;
|
|
3116
3649
|
if (visible && xrayEnabled) {
|
|
3117
3650
|
if (!xrayStyle.isConnected) document.head.appendChild(xrayStyle);
|
|
@@ -3237,45 +3770,221 @@ function initComty(options = {}) {
|
|
|
3237
3770
|
}
|
|
3238
3771
|
document.addEventListener("pointercancel", cancelRulerDrag, true);
|
|
3239
3772
|
|
|
3240
|
-
function
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
if (
|
|
3245
|
-
|
|
3246
|
-
|
|
3773
|
+
function colorDetails(value, token = "") {
|
|
3774
|
+
const channels = /^#([\da-f]{6})$/i.exec(value)
|
|
3775
|
+
? value.slice(1).match(/../g).map(part => parseInt(part, 16))
|
|
3776
|
+
: /^rgba?\((.*)\)$/i.exec(value)?.[1].replace(/\s*\/\s*/g, " ").split(/[\s,]+/).filter(Boolean);
|
|
3777
|
+
if (!channels || channels.length < 3) return null;
|
|
3778
|
+
const [r, g, b] = channels.slice(0, 3).map(part => Math.max(0, Math.min(255, Math.round(part.endsWith?.("%") ? parseFloat(part) * 2.55 : Number(part)))));
|
|
3779
|
+
if (![r, g, b].every(Number.isFinite)) return null;
|
|
3780
|
+
const alpha = channels[3] == null ? 1 : Math.max(0, Math.min(1, channels[3].endsWith("%") ? parseFloat(channels[3]) / 100 : Number(channels[3])));
|
|
3781
|
+
const hex = `#${[r, g, b].map(n => n.toString(16).padStart(2, "0")).join("").toUpperCase()}${alpha < 1 ? Math.round(alpha * 255).toString(16).padStart(2, "0").toUpperCase() : ""}`;
|
|
3782
|
+
const [red, green, blue] = [r, g, b].map(n => n / 255);
|
|
3783
|
+
const max = Math.max(red, green, blue), min = Math.min(red, green, blue), delta = max - min;
|
|
3784
|
+
let hue = 0;
|
|
3785
|
+
if (delta) hue = max === red ? ((green - blue) / delta) % 6 : max === green ? (blue - red) / delta + 2 : (red - green) / delta + 4;
|
|
3786
|
+
hue = (hue * 60 + 360) % 360;
|
|
3787
|
+
const lightness = (max + min) / 2;
|
|
3788
|
+
const saturation = delta ? delta / (1 - Math.abs(2 * lightness - 1)) : 0;
|
|
3789
|
+
const linear = [red, green, blue].map(n => n <= .04045 ? n / 12.92 : ((n + .055) / 1.055) ** 2.4);
|
|
3790
|
+
const l = Math.cbrt(.4122214708 * linear[0] + .5363325363 * linear[1] + .0514459929 * linear[2]);
|
|
3791
|
+
const m = Math.cbrt(.2119034982 * linear[0] + .6806995451 * linear[1] + .1073969566 * linear[2]);
|
|
3792
|
+
const s = Math.cbrt(.0883024619 * linear[0] + .2817188376 * linear[1] + .6299787005 * linear[2]);
|
|
3793
|
+
const L = .2104542553 * l + .793617785 * m - .0040720468 * s;
|
|
3794
|
+
const a = 1.9779984951 * l - 2.428592205 * m + .4505937099 * s;
|
|
3795
|
+
const bLab = .0259040371 * l + .7827717662 * m - .808675766 * s;
|
|
3796
|
+
const chroma = Math.hypot(a, bLab);
|
|
3797
|
+
const okHue = chroma < .001 ? 0 : (Math.atan2(bLab, a) * 180 / Math.PI + 360) % 360;
|
|
3798
|
+
const suffix = alpha < 1 ? ` / ${Number(alpha.toFixed(2))}` : "";
|
|
3799
|
+
return {
|
|
3800
|
+
key: `${hex}|${token}`, hex, token,
|
|
3801
|
+
name: ({ "#FFFFFF": "White", "#000000": "Black" })[hex] || (token ? token.replace(/^--/, "").replace(/[-_]/g, " ") : "Selected colour"),
|
|
3802
|
+
rgb: alpha < 1 ? `rgba(${r}, ${g}, ${b}, ${Number(alpha.toFixed(2))})` : `rgb(${r}, ${g}, ${b})`,
|
|
3803
|
+
hsl: `hsl(${Math.round(hue)} ${Math.round(saturation * 100)}% ${Math.round(lightness * 100)}%${suffix})`,
|
|
3804
|
+
oklch: `oklch(${(L * 100).toFixed(1)}% ${chroma.toFixed(3)} ${Math.round(okHue)}${suffix})`,
|
|
3805
|
+
};
|
|
3806
|
+
}
|
|
3807
|
+
|
|
3808
|
+
function fontDetails(element) {
|
|
3809
|
+
const css = getComputedStyle(element);
|
|
3810
|
+
const color = colorDetails(css.color);
|
|
3811
|
+
const values = [css.fontFamily, css.fontSize, css.fontWeight, css.lineHeight, css.letterSpacing, css.color];
|
|
3812
|
+
return {
|
|
3813
|
+
key: values.join("|"), family: css.fontFamily,
|
|
3814
|
+
size: css.fontSize, weight: css.fontWeight, leading: css.lineHeight,
|
|
3815
|
+
tracking: css.letterSpacing === "normal" ? "0px" : css.letterSpacing,
|
|
3816
|
+
color,
|
|
3817
|
+
css: `font-family: ${css.fontFamily};\nfont-size: ${css.fontSize};\nfont-weight: ${css.fontWeight};\nline-height: ${css.lineHeight};\nletter-spacing: ${css.letterSpacing};\ncolor: ${css.color};`,
|
|
3818
|
+
};
|
|
3819
|
+
}
|
|
3820
|
+
|
|
3821
|
+
function rememberProperty(list, item) {
|
|
3822
|
+
const previous = list.findIndex(entry => entry.key === item.key);
|
|
3823
|
+
if (previous !== -1) list.splice(previous, 1);
|
|
3824
|
+
list.unshift(item);
|
|
3825
|
+
list.length = Math.min(list.length, 4);
|
|
3826
|
+
}
|
|
3827
|
+
|
|
3828
|
+
const copyIcon = icon('<rect x="8" y="8" width="12" height="12" rx="2"/><path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2"/>');
|
|
3829
|
+
const checkIcon = icon('<path d="m5 12 4 4 10-10"/>');
|
|
3830
|
+
const copyFeedbackTimers = new WeakMap();
|
|
3831
|
+
function propertyRow(label, value, display = value) {
|
|
3832
|
+
const unavailable = value === "—";
|
|
3833
|
+
return `<div class="comty-property-row"><span>${label}</span><button type="button" data-copy="${escapeInspectorText(value)}" aria-label="Copy ${label}: ${escapeInspectorText(display)}" ${unavailable ? "disabled" : ""}><span>${escapeInspectorText(display)}</span>${unavailable ? "" : `<i class="comty-property-copy-icon" aria-hidden="true">${copyIcon}${checkIcon}</i>`}</button></div>`;
|
|
3834
|
+
}
|
|
3835
|
+
|
|
3836
|
+
function propertyVisibleRect() {
|
|
3837
|
+
const rect = propertyPanel.getBoundingClientRect();
|
|
3838
|
+
const inset = getComputedStyle(propertyPanel).clipPath.match(/^inset\(([^)]+?)(?: round|\))/)?.[1].split(/\s+/).map(parseFloat) || [0];
|
|
3839
|
+
const [top, right = top, bottom = top, left = right] = inset;
|
|
3840
|
+
return { top: rect.top + top, right: rect.right - right, bottom: rect.bottom - bottom, left: rect.left + left };
|
|
3841
|
+
}
|
|
3842
|
+
|
|
3843
|
+
function renderPropertyPanel(morph = false) {
|
|
3844
|
+
if (!propertyKind) return;
|
|
3845
|
+
const before = morph && !propertyPanel.hidden ? propertyVisibleRect() : null;
|
|
3846
|
+
const oldDetail = propertyPanel.querySelector(".comty-property-detail")?.cloneNode(true);
|
|
3847
|
+
const oldStrip = propertyPanel.querySelector(".comty-property-strip > :first-child")?.getBoundingClientRect();
|
|
3848
|
+
propertyAnimation?.cancel(); propertyAnimation = null;
|
|
3849
|
+
propertyStripAnimation?.cancel(); propertyStripAnimation = null;
|
|
3850
|
+
propertyPanel.style.width = "";
|
|
3851
|
+
const expanded = Boolean(propertySelection);
|
|
3852
|
+
propertyPanel.dataset.kind = propertyKind;
|
|
3853
|
+
propertyPanel.toggleAttribute("data-expanded", expanded);
|
|
3854
|
+
propertyPanel.setAttribute("aria-label", propertyKind === "color" ? "Recent colours" : "Recent typography");
|
|
3855
|
+
const recent = propertyKind === "color" ? recentColors : recentFonts;
|
|
3856
|
+
const details = propertyKind === "color" && expanded
|
|
3857
|
+
? `<div class="comty-property-detail"><div class="comty-property-rows">${propertyRow("HEX", propertySelection.hex)}${propertyRow("RGB", propertySelection.rgb)}${propertyRow("HSL", propertySelection.hsl)}${propertyRow("OKLCH", propertySelection.oklch)}${propertyRow("Token", propertySelection.token ? `var(${propertySelection.token})` : "—")}</div></div>`
|
|
3858
|
+
: propertyKind === "font" && expanded
|
|
3859
|
+
? `<div class="comty-property-detail"><div class="comty-property-rows">${propertyRow("Family", propertySelection.family)}${propertyRow("Size", propertySelection.size)}${propertyRow("Weight", propertySelection.weight)}${propertyRow("Leading", propertySelection.leading)}${propertyRow("Tracking", propertySelection.tracking)}${propertyRow("Colour", propertySelection.color?.hex || "—")}${propertyRow("CSS", propertySelection.css, "All of the above")}</div></div>`
|
|
3860
|
+
: "";
|
|
3861
|
+
const slots = Array.from({ length: 4 }, (_, index) => {
|
|
3862
|
+
const item = recent[index];
|
|
3863
|
+
if (!item) return '<span class="comty-property-empty" aria-hidden="true"></span>';
|
|
3864
|
+
const label = propertyKind === "color" ? `${item.name}, ${item.hex}` : `Font ${index + 1}: ${item.family}`;
|
|
3865
|
+
return `<button type="button" class="comty-property-recent" data-recent="${index}" aria-label="${escapeInspectorText(label)}" aria-pressed="${String(propertySelection?.key === item.key)}">${propertyKind === "font" ? "Aa" : ""}</button>`;
|
|
3866
|
+
}).join("");
|
|
3867
|
+
propertyPanel.innerHTML = `${details}<div class="comty-property-strip">${slots}</div><span class="comty-property-feedback" role="status"></span>`;
|
|
3868
|
+
propertyPanel.hidden = false;
|
|
3869
|
+
if (propertyKind === "color") {
|
|
3870
|
+
propertyPanel.querySelectorAll(".comty-property-recent").forEach((button, index) => { button.style.backgroundColor = recent[index].hex; });
|
|
3871
|
+
} else {
|
|
3872
|
+
propertyPanel.querySelectorAll(".comty-property-recent").forEach((button, index) => { button.style.fontFamily = recent[index].family; button.style.fontWeight = recent[index].weight; });
|
|
3873
|
+
const colourRow = propertyPanel.querySelector('.comty-property-row button[aria-label^="Copy Colour"]');
|
|
3874
|
+
if (colourRow && propertySelection.color) colourRow.querySelector("span").style.setProperty("--comty-picked-color", propertySelection.color.hex);
|
|
3247
3875
|
}
|
|
3248
3876
|
positionToolbar();
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
const
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3877
|
+
if (reducedMotion.matches || !pointerInput || !morph || !before) return;
|
|
3878
|
+
const target = propertyPanel.getBoundingClientRect();
|
|
3879
|
+
const targetStrip = propertyPanel.querySelector(".comty-property-strip > :first-child").getBoundingClientRect();
|
|
3880
|
+
// Keep the outgoing detail until the frame has contracted around the recent values.
|
|
3881
|
+
if (!expanded && oldDetail) {
|
|
3882
|
+
propertyPanel.prepend(oldDetail);
|
|
3883
|
+
propertyPanel.style.width = getComputedStyle(root).getPropertyValue("--comty-property-width").trim();
|
|
3884
|
+
positionToolbar();
|
|
3885
|
+
}
|
|
3886
|
+
const layout = propertyPanel.getBoundingClientRect();
|
|
3887
|
+
const strip = propertyPanel.querySelector(".comty-property-strip");
|
|
3888
|
+
const stripRect = strip.firstElementChild.getBoundingClientRect();
|
|
3889
|
+
const radius = getComputedStyle(propertyPanel).borderRadius;
|
|
3890
|
+
const clip = rect => `inset(${Math.max(0, rect.top - layout.top)}px ${Math.max(0, layout.right - rect.right)}px ${Math.max(0, layout.bottom - rect.bottom)}px ${Math.max(0, rect.left - layout.left)}px round ${radius})`;
|
|
3891
|
+
const css = getComputedStyle(root);
|
|
3892
|
+
const timing = { duration: parseFloat(css.getPropertyValue("--comty-motion-duration")), easing: css.getPropertyValue("--comty-ease-in-out").trim(), fill: "forwards" };
|
|
3893
|
+
propertyAnimation = propertyPanel.animate([{ clipPath: clip(before) }, { clipPath: clip(target) }], timing);
|
|
3894
|
+
propertyStripAnimation = strip.animate([
|
|
3895
|
+
{ transform: `translate(${oldStrip.left - stripRect.left}px, ${oldStrip.top - stripRect.top}px)` },
|
|
3896
|
+
{ transform: `translate(${targetStrip.left - stripRect.left}px, ${targetStrip.top - stripRect.top}px)` },
|
|
3897
|
+
], timing);
|
|
3898
|
+
const animation = propertyAnimation;
|
|
3263
3899
|
animation.onfinish = () => {
|
|
3264
|
-
if (
|
|
3265
|
-
|
|
3900
|
+
if (propertyAnimation !== animation) return;
|
|
3901
|
+
propertyAnimation = null;
|
|
3902
|
+
if (!expanded) { oldDetail?.remove(); propertyPanel.style.width = ""; }
|
|
3903
|
+
propertyStripAnimation?.cancel(); propertyStripAnimation = null;
|
|
3266
3904
|
animation.cancel();
|
|
3267
|
-
|
|
3905
|
+
positionToolbar();
|
|
3268
3906
|
};
|
|
3269
3907
|
}
|
|
3270
3908
|
|
|
3271
|
-
function
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3909
|
+
function openPropertyPanel(kind, element, property, opener = null, sampledColor = null) {
|
|
3910
|
+
propertyPicking = null;
|
|
3911
|
+
colorMagnifier.setEnabled(false);
|
|
3912
|
+
delete document.documentElement.dataset.comtyPropertyPicking;
|
|
3913
|
+
if (motion.enabled) setMotionEnabled(false);
|
|
3914
|
+
gridPanelOpen = false;
|
|
3915
|
+
toolPanel.hidden = true;
|
|
3916
|
+
gridBtn.setAttribute("aria-expanded", "false");
|
|
3917
|
+
const item = kind === "color" && element
|
|
3918
|
+
? sampledColor ? colorDetails(sampledColor, inspectedBackgroundToken(sampledColor, element) || "") : colorDetails(getComputedStyle(element)[property === "background" ? "backgroundColor" : "color"], getColorTokens(element)[property === "background" ? "background" : "color"].at(0) || "")
|
|
3919
|
+
: kind === "font" && element ? fontDetails(element) : null;
|
|
3920
|
+
if (item) rememberProperty(kind === "color" ? recentColors : recentFonts, item);
|
|
3921
|
+
propertyOpener = opener;
|
|
3922
|
+
propertyKind = kind;
|
|
3923
|
+
propertySelection = null;
|
|
3924
|
+
for (const [button, active] of [[pipetteBtn, kind === "color"], [typographyBtn, kind === "font"]]) {
|
|
3925
|
+
button.setAttribute("aria-pressed", String(active));
|
|
3926
|
+
button.setAttribute("aria-expanded", String(active));
|
|
3927
|
+
}
|
|
3928
|
+
renderPropertyPanel();
|
|
3929
|
+
}
|
|
3930
|
+
|
|
3931
|
+
function togglePropertyPanel(kind, opener, screenSampling = false) {
|
|
3932
|
+
if (!screenSampling && !propertyPanel.hidden && propertyKind === kind) { closePropertyPanel(); return; }
|
|
3933
|
+
openPropertyPanel(kind, null, "color", opener);
|
|
3934
|
+
rulersEnabled = false;
|
|
3935
|
+
syncInspectorTools();
|
|
3936
|
+
if (kind === "color" && screenSampling && window.EyeDropper) sampleColor();
|
|
3937
|
+
else {
|
|
3938
|
+
propertyPicking = kind;
|
|
3939
|
+
colorMagnifier.setEnabled(kind === "color");
|
|
3940
|
+
document.documentElement.dataset.comtyPropertyPicking = kind;
|
|
3941
|
+
selectedElement = null;
|
|
3942
|
+
hideInspector();
|
|
3943
|
+
}
|
|
3277
3944
|
}
|
|
3278
3945
|
|
|
3946
|
+
function closePropertyPanel(restoreFocus = false) {
|
|
3947
|
+
if (propertyPanel.hidden) return;
|
|
3948
|
+
if (propertyPicking === "font") hideInspector();
|
|
3949
|
+
propertyAnimation?.cancel(); propertyAnimation = null;
|
|
3950
|
+
propertyStripAnimation?.cancel(); propertyStripAnimation = null;
|
|
3951
|
+
colorPickerController?.abort();
|
|
3952
|
+
propertyPanel.hidden = true;
|
|
3953
|
+
propertyKind = null; propertySelection = null; propertyPicking = null;
|
|
3954
|
+
colorMagnifier.setEnabled(false);
|
|
3955
|
+
delete document.documentElement.dataset.comtyPropertyPicking;
|
|
3956
|
+
for (const button of [pipetteBtn, typographyBtn]) {
|
|
3957
|
+
button.setAttribute("aria-pressed", "false");
|
|
3958
|
+
button.setAttribute("aria-expanded", "false");
|
|
3959
|
+
}
|
|
3960
|
+
if (restoreFocus && propertyOpener?.isConnected) propertyOpener.focus({ preventScroll: true });
|
|
3961
|
+
propertyOpener = null;
|
|
3962
|
+
}
|
|
3963
|
+
|
|
3964
|
+
propertyPanel.onclick = async event => {
|
|
3965
|
+
const button = event.target.closest("button");
|
|
3966
|
+
if (!button) return;
|
|
3967
|
+
if (button.dataset.recent != null) {
|
|
3968
|
+
const item = (propertyKind === "color" ? recentColors : recentFonts)[Number(button.dataset.recent)];
|
|
3969
|
+
propertySelection = propertySelection?.key === item.key ? null : item;
|
|
3970
|
+
renderPropertyPanel(true);
|
|
3971
|
+
propertyPanel.querySelector(`button[data-recent="${button.dataset.recent}"]`)?.focus({ preventScroll: true });
|
|
3972
|
+
} else if (button.dataset.copy != null && button.dataset.copy !== "—") {
|
|
3973
|
+
clearTimeout(copyFeedbackTimers.get(button));
|
|
3974
|
+
try {
|
|
3975
|
+
await navigator.clipboard.writeText(button.dataset.copy);
|
|
3976
|
+
if (!button.isConnected || propertyPanel.hidden) return;
|
|
3977
|
+
button.dataset.copied = "";
|
|
3978
|
+
propertyPanel.querySelector(".comty-property-feedback").textContent = `${button.getAttribute("aria-label").slice(5)} copied`;
|
|
3979
|
+
clearTimeout(copyFeedbackTimers.get(button));
|
|
3980
|
+
copyFeedbackTimers.set(button, setTimeout(() => { delete button.dataset.copied; }, 1200));
|
|
3981
|
+
} catch {
|
|
3982
|
+
delete button.dataset.copied;
|
|
3983
|
+
if (button.isConnected && !propertyPanel.hidden) propertyPanel.querySelector(".comty-property-feedback").textContent = "Clipboard unavailable";
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3986
|
+
};
|
|
3987
|
+
|
|
3279
3988
|
function inspectedBackgroundToken(hex, element) {
|
|
3280
3989
|
if (!element?.isConnected || isElementTreeHidden(element)) return null;
|
|
3281
3990
|
const sampled = `rgb(${hex.slice(1).match(/../g).map(part => parseInt(part, 16)).join(', ')})`;
|
|
@@ -3293,53 +4002,42 @@ function initComty(options = {}) {
|
|
|
3293
4002
|
async function sampleColor() {
|
|
3294
4003
|
if (colorPickerController) return;
|
|
3295
4004
|
const inspectedBackground = selectedElement?.isConnected && !isElementTreeHidden(selectedElement) ? selectedElement : inspectedElement;
|
|
3296
|
-
setMotionEnabled(false);
|
|
3297
|
-
gridPanelOpen = false;
|
|
3298
|
-
renderGridPanel();
|
|
3299
|
-
toolPanel.hidden = false;
|
|
3300
|
-
toolPanel.setAttribute("aria-label", "Color sampler");
|
|
3301
4005
|
if (!window.EyeDropper) {
|
|
3302
|
-
|
|
3303
|
-
dismissColorResultLater();
|
|
4006
|
+
propertyPanel.querySelector(".comty-property-feedback").textContent = "Screen sampling requires Chrome or Edge";
|
|
3304
4007
|
return;
|
|
3305
4008
|
}
|
|
3306
4009
|
colorPickerController = new AbortController();
|
|
3307
|
-
pipetteBtn.setAttribute("aria-pressed", "true");
|
|
3308
4010
|
// Hide every overlay so the sampler reads the page's original pixels.
|
|
3309
4011
|
const previousVisibility = root.style.visibility;
|
|
3310
4012
|
root.style.visibility = "hidden";
|
|
3311
4013
|
xrayStyle.remove();
|
|
3312
4014
|
try {
|
|
3313
4015
|
const { sRGBHex } = await new window.EyeDropper().open({ signal: colorPickerController.signal });
|
|
4016
|
+
if (colorPickerController.signal.aborted || propertyKind !== "color") return;
|
|
3314
4017
|
if (!/^#[0-9a-f]{6}$/i.test(sRGBHex)) throw new Error("Invalid sampled color");
|
|
3315
|
-
const rgb = sRGBHex.slice(1).match(/../g).map(part => parseInt(part, 16));
|
|
3316
4018
|
const token = inspectedBackgroundToken(sRGBHex, inspectedBackground);
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
try {
|
|
3323
|
-
|
|
3324
|
-
status.textContent = "Copied";
|
|
3325
|
-
} catch { status.textContent = "Select to copy"; }
|
|
4019
|
+
const item = colorDetails(sRGBHex, token || "");
|
|
4020
|
+
rememberProperty(recentColors, item);
|
|
4021
|
+
propertyKind = "color";
|
|
4022
|
+
propertySelection = null;
|
|
4023
|
+
renderPropertyPanel();
|
|
4024
|
+
try { await navigator.clipboard.writeText(item.hex); propertyPanel.querySelector(".comty-property-feedback").textContent = "HEX copied"; }
|
|
4025
|
+
catch { propertyPanel.querySelector(".comty-property-feedback").textContent = "Select a value to copy"; }
|
|
3326
4026
|
} catch (error) {
|
|
3327
|
-
if (error.name
|
|
3328
|
-
else { toolPanel.textContent = "Unable to sample a color. Please try again."; dismissColorResultLater(); }
|
|
4027
|
+
if (error.name !== "AbortError") propertyPanel.querySelector(".comty-property-feedback").textContent = "Unable to sample a colour";
|
|
3329
4028
|
} finally {
|
|
3330
4029
|
root.style.visibility = previousVisibility;
|
|
3331
|
-
if (toolPanel.classList.contains("comty-color-toast")) animateColorToast();
|
|
3332
4030
|
colorPickerController = null;
|
|
3333
|
-
pipetteBtn.setAttribute("aria-pressed", "false");
|
|
3334
4031
|
if (session.inspect && xrayEnabled) document.head.appendChild(xrayStyle);
|
|
3335
4032
|
}
|
|
3336
4033
|
}
|
|
3337
4034
|
document.addEventListener("pointerdown", event => {
|
|
3338
4035
|
if (!toolPanel.hidden && !toolPanel.contains(event.target) && !toolbar.contains(event.target)) {
|
|
3339
4036
|
gridPanelOpen = false;
|
|
3340
|
-
|
|
4037
|
+
toolPanel.hidden = true;
|
|
3341
4038
|
gridBtn.setAttribute("aria-expanded", "false");
|
|
3342
4039
|
}
|
|
4040
|
+
if (!propertyPicking && !propertyPanel.hidden && !propertyPanel.contains(event.target) && !pipetteBtn.contains(event.target) && !typographyBtn.contains(event.target) && !event.target.closest?.(".comty-inspector-picker")) closePropertyPanel();
|
|
3343
4041
|
}, true);
|
|
3344
4042
|
|
|
3345
4043
|
const sidepanelBtn = document.createElement("button");
|
|
@@ -3400,11 +4098,56 @@ function initComty(options = {}) {
|
|
|
3400
4098
|
let toolbarMotionFrame = 0;
|
|
3401
4099
|
let toolbarAnimations = [];
|
|
3402
4100
|
const reducedMotion = matchMedia("(prefers-reduced-motion: reduce)");
|
|
4101
|
+
let workingIndicatorTimer = 0;
|
|
4102
|
+
function scheduleWorkingIndicators() {
|
|
4103
|
+
if (workingIndicatorTimer || reducedMotion.matches || document.hidden) return;
|
|
4104
|
+
workingIndicatorTimer = setTimeout(() => {
|
|
4105
|
+
workingIndicatorTimer = 0;
|
|
4106
|
+
if (!root.isConnected) return;
|
|
4107
|
+
const indicators = root.querySelectorAll('.comty-working-indicator');
|
|
4108
|
+
if (!indicators.length) return;
|
|
4109
|
+
for (const indicator of indicators) {
|
|
4110
|
+
const previous = Number(indicator.dataset.activeDots.split(',')[0]);
|
|
4111
|
+
const first = (previous + 1 + Math.floor(Math.random() * 8)) % 9;
|
|
4112
|
+
const second = (first + 1 + Math.floor(Math.random() * 8)) % 9;
|
|
4113
|
+
indicator.dataset.activeDots = `${first},${second}`;
|
|
4114
|
+
[...indicator.children].forEach((dot, index) => dot.toggleAttribute('data-active', index === first || index === second));
|
|
4115
|
+
}
|
|
4116
|
+
scheduleWorkingIndicators();
|
|
4117
|
+
}, 600);
|
|
4118
|
+
}
|
|
4119
|
+
function createWorkingIndicator() {
|
|
4120
|
+
const indicator = document.createElement('span');
|
|
4121
|
+
indicator.className = 'comty-working-indicator';
|
|
4122
|
+
indicator.setAttribute('aria-hidden', 'true');
|
|
4123
|
+
indicator.dataset.activeDots = '3,5';
|
|
4124
|
+
for (let index = 0; index < 9; index++) {
|
|
4125
|
+
const dot = document.createElement('i');
|
|
4126
|
+
dot.toggleAttribute('data-active', index === 3 || index === 5);
|
|
4127
|
+
indicator.appendChild(dot);
|
|
4128
|
+
}
|
|
4129
|
+
scheduleWorkingIndicators();
|
|
4130
|
+
return indicator;
|
|
4131
|
+
}
|
|
4132
|
+
function resetWorkingIndicators() {
|
|
4133
|
+
clearTimeout(workingIndicatorTimer);
|
|
4134
|
+
workingIndicatorTimer = 0;
|
|
4135
|
+
if (reducedMotion.matches) {
|
|
4136
|
+
for (const indicator of root.querySelectorAll('.comty-working-indicator')) {
|
|
4137
|
+
indicator.dataset.activeDots = '3,5';
|
|
4138
|
+
[...indicator.children].forEach((dot, index) => dot.toggleAttribute('data-active', index === 3 || index === 5));
|
|
4139
|
+
}
|
|
4140
|
+
}
|
|
4141
|
+
if (root.querySelector('.comty-working-indicator')) scheduleWorkingIndicators();
|
|
4142
|
+
}
|
|
4143
|
+
reducedMotion.addEventListener('change', resetWorkingIndicators);
|
|
4144
|
+
document.addEventListener('visibilitychange', resetWorkingIndicators);
|
|
3403
4145
|
const clampToolbar = (value, size, viewport) => Math.max(8, Math.min(value, viewport - size - 8));
|
|
3404
4146
|
function rememberToolbarPosition(left, top) {
|
|
3405
4147
|
const width = toolbar.offsetWidth, height = toolbar.offsetHeight;
|
|
3406
4148
|
const centerX = left + width / 2, centerY = top + height / 2;
|
|
3407
|
-
const
|
|
4149
|
+
const availableWidth = innerWidth - width;
|
|
4150
|
+
const horizontal = left < availableWidth / 3 ? "left" : left > availableWidth * 2 / 3 ? "right" : "center";
|
|
3408
4151
|
const vertical = centerY < innerHeight / 2 ? "top" : "bottom";
|
|
3409
4152
|
toolbarPosition = {
|
|
3410
4153
|
horizontal, vertical,
|
|
@@ -3432,20 +4175,26 @@ function initComty(options = {}) {
|
|
|
3432
4175
|
});
|
|
3433
4176
|
}
|
|
3434
4177
|
const rect = toolbar.getBoundingClientRect();
|
|
3435
|
-
for (const panel of [toolPanel, sidepanel, motionPanel]) {
|
|
4178
|
+
for (const panel of [toolPanel, propertyPanel, sidepanel, motionPanel]) {
|
|
3436
4179
|
if (panel.hidden || (panel === sidepanel && !sidepanelOpen)) continue;
|
|
3437
4180
|
const above = rect.top - 16;
|
|
3438
4181
|
const below = innerHeight - rect.bottom - 16;
|
|
3439
|
-
const useAbove = above >= panel.scrollHeight || above >= below;
|
|
4182
|
+
const useAbove = panel === propertyPanel ? above >= below : above >= panel.scrollHeight || above >= below;
|
|
3440
4183
|
panel.style.maxHeight = `${Math.max(0, useAbove ? above : below)}px`;
|
|
3441
|
-
|
|
4184
|
+
const edgeThreshold = toolbar.offsetHeight;
|
|
4185
|
+
const alignment = rect.left <= edgeThreshold ? "left" : innerWidth - rect.right <= edgeThreshold ? "right" : "center";
|
|
4186
|
+
const left = panel === propertyPanel
|
|
4187
|
+
? alignment === "left" ? rect.left : alignment === "right" ? rect.right - panel.offsetWidth : rect.left + (rect.width - panel.offsetWidth) / 2
|
|
4188
|
+
: rect.right - panel.offsetWidth;
|
|
4189
|
+
if (panel === propertyPanel) panel.dataset.alignment = alignment;
|
|
4190
|
+
panel.style.left = `${Math.max(8, Math.min(left, innerWidth - panel.offsetWidth - 8))}px`;
|
|
3442
4191
|
panel.style.right = "auto";
|
|
3443
4192
|
panel.style.top = `${useAbove ? rect.top - panel.offsetHeight - 8 : rect.bottom + 8}px`;
|
|
3444
4193
|
panel.style.bottom = "auto";
|
|
3445
4194
|
}
|
|
3446
4195
|
}
|
|
3447
4196
|
const toolbarResize = new ResizeObserver(positionToolbar);
|
|
3448
|
-
[toolbar, toolPanel, sidepanel, motionPanel].forEach(node => toolbarResize.observe(node));
|
|
4197
|
+
[toolbar, toolPanel, propertyPanel, sidepanel, motionPanel].forEach(node => toolbarResize.observe(node));
|
|
3449
4198
|
window.addEventListener("resize", positionToolbar);
|
|
3450
4199
|
|
|
3451
4200
|
toolbar.addEventListener("pointerdown", event => {
|
|
@@ -3505,8 +4254,11 @@ function initComty(options = {}) {
|
|
|
3505
4254
|
|
|
3506
4255
|
function prepareToolbarMotion() {
|
|
3507
4256
|
if (toolbarMotionFrame) return;
|
|
3508
|
-
const nodes = [toolbarSurface,
|
|
3509
|
-
|
|
4257
|
+
const nodes = [toolbarSurface, dragHandle, primaryTools, commentTools, agentTools, inspectorTools].filter(Boolean);
|
|
4258
|
+
// Move each group's background with its icons, including when interrupted.
|
|
4259
|
+
const before = new Map(nodes.filter(node => !node.hidden).map(node => [node, {
|
|
4260
|
+
rect: node.getBoundingClientRect(), opacity: getComputedStyle(node).opacity,
|
|
4261
|
+
}]));
|
|
3510
4262
|
stopToolbarMotion();
|
|
3511
4263
|
toolbarMotionFrame = requestAnimationFrame(() => {
|
|
3512
4264
|
toolbarMotionFrame = 0;
|
|
@@ -3516,8 +4268,8 @@ function initComty(options = {}) {
|
|
|
3516
4268
|
const timing = { duration: parseFloat(css.getPropertyValue("--comty-motion-duration")), easing: css.getPropertyValue("--comty-ease-out").trim() };
|
|
3517
4269
|
for (const node of nodes.filter(node => !node.hidden)) {
|
|
3518
4270
|
const next = node.getBoundingClientRect(), previous = before.get(node);
|
|
3519
|
-
const transform = previous ? `translate(${previous.left - next.left}px, ${previous.top - next.top}px)${node === toolbarSurface ? ` scale(${previous.width / next.width}, ${previous.height / next.height})` : ""}` : "
|
|
3520
|
-
toolbarAnimations.push(node.animate([{ transform, opacity: previous
|
|
4271
|
+
const transform = previous ? `translate(${previous.rect.left - next.left}px, ${previous.rect.top - next.top}px)${node === toolbarSurface ? ` scale(${previous.rect.width / next.width}, ${previous.rect.height / next.height})` : ""}` : "none";
|
|
4272
|
+
toolbarAnimations.push(node.animate([{ transform, opacity: previous?.opacity ?? 0 }, { transform: "none", opacity: 1 }], timing));
|
|
3521
4273
|
}
|
|
3522
4274
|
});
|
|
3523
4275
|
}
|
|
@@ -3714,8 +4466,10 @@ function initComty(options = {}) {
|
|
|
3714
4466
|
if (mode === "off" || mode === "inspect" || session.inspect) {
|
|
3715
4467
|
if (mode === "inspect" && session.inspect) return;
|
|
3716
4468
|
setMotionEnabled(false);
|
|
4469
|
+
closePropertyPanel();
|
|
3717
4470
|
colorPickerController?.abort();
|
|
3718
4471
|
cancelRulerDrag();
|
|
4472
|
+
rulersEnabled = false;
|
|
3719
4473
|
gridPanelOpen = false;
|
|
3720
4474
|
toolPanel.hidden = true;
|
|
3721
4475
|
measuredRegion = null;
|
|
@@ -3775,31 +4529,52 @@ function initComty(options = {}) {
|
|
|
3775
4529
|
return;
|
|
3776
4530
|
}
|
|
3777
4531
|
document.querySelector(".comty-auth-backdrop")?.remove();
|
|
4532
|
+
const opener = document.activeElement;
|
|
3778
4533
|
const backdrop = document.createElement("div");
|
|
3779
4534
|
backdrop.className = "comty-auth-backdrop";
|
|
3780
4535
|
backdrop.innerHTML = `
|
|
3781
|
-
<div class="comty-auth-dialog" role="dialog" aria-modal="true" aria-labelledby="comty-auth-title">
|
|
4536
|
+
<div class="comty-auth-dialog" role="dialog" aria-modal="true" aria-labelledby="comty-auth-title" aria-describedby="comty-auth-description" tabindex="-1">
|
|
4537
|
+
<div class="comty-auth-header">
|
|
4538
|
+
<span class="comty-auth-icon">${icon('<path d="M21 11.5a8.4 8.4 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.4 8.4 0 0 1-3.8-.9L3 21l1.9-5.7a8.5 8.5 0 1 1 16.1-3.8Z"/>')}</span>
|
|
4539
|
+
<button type="button" class="comty-auth-close" data-comty-close aria-label="Close sign-in dialog">${icon('<path d="m6 6 12 12M6 18 18 6"/>')}</button>
|
|
4540
|
+
</div>
|
|
3782
4541
|
<h2 id="comty-auth-title">${forbidden ? "Project access required" : "Sign in to comment"}</h2>
|
|
3783
|
-
<p>${forbidden ? "This account is not a member of this project. Ask the project owner for an invite." : "
|
|
4542
|
+
<p id="comty-auth-description">${forbidden ? "This account is not a member of this project. Ask the project owner for an invite." : "Sign in to share feedback with your team and keep the conversation in one place."}</p>
|
|
3784
4543
|
<div class="comty-auth-actions">
|
|
3785
|
-
<button type="button" data-comty-close
|
|
4544
|
+
<button type="button" data-comty-close>${forbidden ? "Close" : "Cancel"}</button>
|
|
3786
4545
|
${forbidden ? "" : '<button type="button" class="comty-auth-primary" data-comty-login>Continue to sign in</button>'}
|
|
3787
4546
|
</div>
|
|
3788
4547
|
</div>`;
|
|
3789
|
-
|
|
4548
|
+
const close = () => {
|
|
4549
|
+
backdrop.remove();
|
|
4550
|
+
if (opener?.isConnected) opener.focus({ preventScroll: true });
|
|
4551
|
+
};
|
|
4552
|
+
backdrop.querySelectorAll("[data-comty-close]").forEach(button => { button.onclick = close; });
|
|
4553
|
+
backdrop.addEventListener("keydown", event => {
|
|
4554
|
+
event.stopPropagation();
|
|
4555
|
+
if (event.key === "Escape") { event.preventDefault(); close(); }
|
|
4556
|
+
if (event.key === "Tab") {
|
|
4557
|
+
const buttons = [...backdrop.querySelectorAll("button:not(:disabled)")];
|
|
4558
|
+
if (!buttons.length) { event.preventDefault(); return; }
|
|
4559
|
+
const first = buttons[0], last = buttons.at(-1);
|
|
4560
|
+
if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus(); }
|
|
4561
|
+
else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); }
|
|
4562
|
+
}
|
|
4563
|
+
});
|
|
3790
4564
|
const loginButton = backdrop.querySelector("[data-comty-login]");
|
|
3791
4565
|
if (loginButton) {
|
|
3792
4566
|
loginButton.onclick = () => {
|
|
3793
4567
|
loginButton.disabled = true;
|
|
3794
4568
|
loginButton.innerText = "Connecting…";
|
|
3795
|
-
backdrop.
|
|
4569
|
+
backdrop.querySelectorAll("[data-comty-close]").forEach(button => { button.disabled = true; });
|
|
3796
4570
|
setTimeout(() => window.location.assign(loginUrl), 50);
|
|
3797
4571
|
};
|
|
3798
4572
|
}
|
|
3799
4573
|
backdrop.onclick = (event) => {
|
|
3800
|
-
if (event.target === backdrop)
|
|
4574
|
+
if (event.target === backdrop) close();
|
|
3801
4575
|
};
|
|
3802
4576
|
root.appendChild(backdrop);
|
|
4577
|
+
(loginButton || backdrop.querySelector("[data-comty-close]")).focus({ preventScroll: true });
|
|
3803
4578
|
}
|
|
3804
4579
|
|
|
3805
4580
|
function formatBoxValue(value) {
|
|
@@ -3909,7 +4684,7 @@ function initComty(options = {}) {
|
|
|
3909
4684
|
};
|
|
3910
4685
|
try {
|
|
3911
4686
|
for (const sheet of [...document.styleSheets, ...document.adoptedStyleSheets]) {
|
|
3912
|
-
if (sheet.disabled || sheet.ownerNode?.closest?.('#comty-root') || sheet.ownerNode === style) continue;
|
|
4687
|
+
if (sheet.disabled || sheet.ownerNode?.closest?.('#comty-root') || sheet.ownerNode === style || sheet.ownerNode?.matches?.('link[data-comty-font]')) continue;
|
|
3913
4688
|
if (sheet.media?.mediaText && !matchMedia(sheet.media.mediaText).matches) continue;
|
|
3914
4689
|
visit(sheet.cssRules);
|
|
3915
4690
|
}
|
|
@@ -4114,7 +4889,29 @@ function initComty(options = {}) {
|
|
|
4114
4889
|
}
|
|
4115
4890
|
|
|
4116
4891
|
function renderInspector() {
|
|
4117
|
-
if (
|
|
4892
|
+
if (propertyPicking === "font") {
|
|
4893
|
+
if (!inspectedElement?.isConnected || isElementTreeHidden(inspectedElement)) { hideInspector(); return; }
|
|
4894
|
+
let box = inspectionLayer.querySelector(".comty-font-target");
|
|
4895
|
+
if (!box) {
|
|
4896
|
+
box = document.createElement("div");
|
|
4897
|
+
box.className = "comty-font-target";
|
|
4898
|
+
inspectionLayer.replaceChildren(box);
|
|
4899
|
+
}
|
|
4900
|
+
const rect = inspectedElement.getBoundingClientRect();
|
|
4901
|
+
// Tight line-height can leave glyphs outside the element's layout box.
|
|
4902
|
+
const range = document.createRange();
|
|
4903
|
+
range.selectNodeContents(inspectedElement);
|
|
4904
|
+
const text = range.getBoundingClientRect();
|
|
4905
|
+
const left = Math.min(rect.left, text.width ? text.left : rect.left);
|
|
4906
|
+
const top = Math.min(rect.top, text.height ? text.top : rect.top);
|
|
4907
|
+
setMeasuredRect(box, {
|
|
4908
|
+
left, top,
|
|
4909
|
+
width: Math.max(rect.right, text.width ? text.right : rect.right) - left,
|
|
4910
|
+
height: Math.max(rect.bottom, text.height ? text.bottom : rect.bottom) - top,
|
|
4911
|
+
});
|
|
4912
|
+
return;
|
|
4913
|
+
}
|
|
4914
|
+
if (propertyPicking || motion.enabled || (session.inspect && rulersEnabled)) {
|
|
4118
4915
|
if (inspectionLayer.childNodes.length) hideInspector();
|
|
4119
4916
|
return;
|
|
4120
4917
|
}
|
|
@@ -4189,13 +4986,26 @@ function initComty(options = {}) {
|
|
|
4189
4986
|
panel.setAttribute("aria-label", "Element properties");
|
|
4190
4987
|
if (selected) panel.dataset.selected = "";
|
|
4191
4988
|
const copyable = (value) => `<button type="button" title="Copy ${escapeInspectorText(value)}" data-copy="${escapeInspectorText(value)}">${escapeInspectorText(value)}</button>`;
|
|
4192
|
-
|
|
4989
|
+
const featured = new Set(["background", "color", "font", "font-family"]);
|
|
4990
|
+
const visibleRows = inspectExpanded ? rows : selected ? [...rows.filter(([key]) => featured.has(key)), ...rows.filter(([key]) => !featured.has(key))].slice(0, 6) : rows.slice(0, 6);
|
|
4991
|
+
panel.innerHTML = `<div class="comty-inspector-head">${copyable(selector)}${copyable(`${formatBoxValue(rect.width)} × ${formatBoxValue(rect.height)}`)}</div><div class="comty-inspector-details">${visibleRows.map(([key, value, tokens]) => {
|
|
4992
|
+
const picker = key === "background" || key === "color" ? "color" : key === "font" || key === "font-family" ? "font" : "";
|
|
4993
|
+
const valueButton = picker && selected
|
|
4994
|
+
? `<button type="button" class="comty-inspector-picker" data-picker="${picker}" data-property="${key}" aria-label="Open ${picker === "color" ? key + " colour" : "typography"} details">${picker === "color" ? '<span class="comty-inspector-swatch" aria-hidden="true"></span>' : ""}${escapeInspectorText(value)}</button>`
|
|
4995
|
+
: copyable(value);
|
|
4996
|
+
return `<div class="comty-inspector-row"><span>${key}</span><div class="comty-inspector-value">${tokens?.length ? `<div class="comty-inspector-tokens">${tokens.map(token => copyable(token)).join("")}</div>` : ""}${valueButton}</div></div>`;
|
|
4997
|
+
}).join("")}</div>${rows.length > 6 && selected ? `<button type="button" class="comty-inspector-expand">${inspectExpanded ? "Show less" : `Show ${rows.length - 6} more`}</button>` : ""}`;
|
|
4998
|
+
for (const button of panel.querySelectorAll('.comty-inspector-picker[data-picker="color"]')) {
|
|
4999
|
+
button.querySelector(".comty-inspector-swatch").style.backgroundColor = getComputedStyle(target)[button.dataset.property === "background" ? "backgroundColor" : "color"];
|
|
5000
|
+
}
|
|
4193
5001
|
panel.onclick = async (event) => {
|
|
4194
5002
|
const button = event.target.closest("button");
|
|
4195
5003
|
if (!button) return;
|
|
4196
5004
|
if (button.classList.contains("comty-inspector-expand")) {
|
|
4197
5005
|
inspectExpanded = !inspectExpanded;
|
|
4198
5006
|
renderInspector();
|
|
5007
|
+
} else if (button.dataset.picker) {
|
|
5008
|
+
openPropertyPanel(button.dataset.picker, target, button.dataset.property, button);
|
|
4199
5009
|
} else {
|
|
4200
5010
|
try {
|
|
4201
5011
|
await navigator.clipboard.writeText(button.dataset.copy);
|
|
@@ -5343,7 +6153,8 @@ function initComty(options = {}) {
|
|
|
5343
6153
|
const failed = Boolean(task.error) || task.status === 'in_review';
|
|
5344
6154
|
const state = failed ? 'error' : task.status === 'done' ? 'success' : task.status === 'cancel' ? 'cancel' : 'working';
|
|
5345
6155
|
const row = node('div', 'comty-agent-row'); row.dataset.taskId = task.id;
|
|
5346
|
-
const dot = node('span', 'comty-agent-dot');
|
|
6156
|
+
const dot = state === 'working' ? createWorkingIndicator() : node('span', 'comty-agent-dot');
|
|
6157
|
+
dot.dataset.status = state; dot.setAttribute('aria-hidden', 'true');
|
|
5347
6158
|
const name = node('span', 'comty-agent-row-name', task.content);
|
|
5348
6159
|
row.append(dot, name);
|
|
5349
6160
|
if (state === 'working') row.appendChild(node('span', 'comty-agent-row-state', task.status === 'todo' ? 'Starting' : 'Working'));
|
|
@@ -6197,7 +7008,9 @@ function initComty(options = {}) {
|
|
|
6197
7008
|
outline = document.createElement("div");
|
|
6198
7009
|
outline.className = "comty-agent-working";
|
|
6199
7010
|
outline.setAttribute("aria-label", "Agent working on this element");
|
|
6200
|
-
const badge = document.createElement("span");
|
|
7011
|
+
const badge = document.createElement("span");
|
|
7012
|
+
badge.className = "comty-agent-working-badge";
|
|
7013
|
+
badge.append(createWorkingIndicator(), "Working");
|
|
6201
7014
|
outline.appendChild(badge); root.appendChild(outline);
|
|
6202
7015
|
workingOutlines.set(id, outline);
|
|
6203
7016
|
}
|
|
@@ -6219,6 +7032,7 @@ function initComty(options = {}) {
|
|
|
6219
7032
|
}
|
|
6220
7033
|
if (session.inspect || session.edit || inspectionLayer.childNodes.length) renderInspector();
|
|
6221
7034
|
if (session.inspect && rulersEnabled) renderRulers();
|
|
7035
|
+
if (session.inspect && rulersEnabled) measurements.render();
|
|
6222
7036
|
}
|
|
6223
7037
|
|
|
6224
7038
|
function ensureLiveTracking() {
|
|
@@ -6310,6 +7124,14 @@ function initComty(options = {}) {
|
|
|
6310
7124
|
document.addEventListener("pointermove", (event) => {
|
|
6311
7125
|
if (!session.edit && !session.inspect) return;
|
|
6312
7126
|
if (motion.enabled) return;
|
|
7127
|
+
if (propertyPicking) {
|
|
7128
|
+
const target = event.target;
|
|
7129
|
+
if (propertyPicking === "font" && target instanceof Element && !isComtyElement(target) && getElementOwnTextSignature(target)) {
|
|
7130
|
+
inspectedElement = target;
|
|
7131
|
+
renderInspector();
|
|
7132
|
+
} else hideInspector();
|
|
7133
|
+
return;
|
|
7134
|
+
}
|
|
6313
7135
|
if (inspectDrag) {
|
|
6314
7136
|
const dx = event.clientX - inspectDrag.x, dy = event.clientY - inspectDrag.y;
|
|
6315
7137
|
if (Math.hypot(dx, dy) > 4) {
|
|
@@ -6341,7 +7163,7 @@ function initComty(options = {}) {
|
|
|
6341
7163
|
if ((!session.inspect && !session.edit) || !(event.target instanceof Element) || isComtyElement(event.target)) return;
|
|
6342
7164
|
if (event.button !== 0) return;
|
|
6343
7165
|
if (motion.enabled) return;
|
|
6344
|
-
if (session.inspect) {
|
|
7166
|
+
if (session.inspect && !propertyPicking) {
|
|
6345
7167
|
inspectDrag = { x: event.clientX, y: event.clientY };
|
|
6346
7168
|
measuredRegion = null;
|
|
6347
7169
|
}
|
|
@@ -6371,6 +7193,19 @@ function initComty(options = {}) {
|
|
|
6371
7193
|
e.preventDefault();
|
|
6372
7194
|
e.stopImmediatePropagation();
|
|
6373
7195
|
if (ignoreInspectClick) return;
|
|
7196
|
+
if (propertyPicking) {
|
|
7197
|
+
const kind = propertyPicking;
|
|
7198
|
+
let element = target;
|
|
7199
|
+
const property = getElementOwnTextSignature(element) ? "color" : "background";
|
|
7200
|
+
if (kind === "color" && property === "background") {
|
|
7201
|
+
while (element.parentElement && getComputedStyle(element).backgroundColor === "rgba(0, 0, 0, 0)") element = element.parentElement;
|
|
7202
|
+
}
|
|
7203
|
+
openPropertyPanel(kind, element, property, kind === "color" ? pipetteBtn : typographyBtn, kind === "color" ? colorMagnifier.sample() : null);
|
|
7204
|
+
propertyPicking = kind;
|
|
7205
|
+
colorMagnifier.setEnabled(kind === "color");
|
|
7206
|
+
document.documentElement.dataset.comtyPropertyPicking = kind;
|
|
7207
|
+
return;
|
|
7208
|
+
}
|
|
6374
7209
|
measuredRegion = null;
|
|
6375
7210
|
selectedElement = getAnchorElement(target);
|
|
6376
7211
|
inspectExpanded = false;
|
|
@@ -6442,15 +7277,16 @@ function initComty(options = {}) {
|
|
|
6442
7277
|
setMotionEnabled(false); motionBtn.focus({ preventScroll: true });
|
|
6443
7278
|
return;
|
|
6444
7279
|
}
|
|
6445
|
-
if (e.key === "Escape" && (!toolPanel.hidden || rulerDrag || colorPickerController)) {
|
|
7280
|
+
if (e.key === "Escape" && (!propertyPanel.hidden || !toolPanel.hidden || rulerDrag || colorPickerController)) {
|
|
6446
7281
|
e.preventDefault();
|
|
6447
|
-
|
|
7282
|
+
closePropertyPanel(true);
|
|
7283
|
+
gridPanelOpen = false; toolPanel.hidden = true;
|
|
6448
7284
|
gridBtn.setAttribute("aria-expanded", "false");
|
|
6449
7285
|
cancelRulerDrag(); colorPickerController?.abort();
|
|
6450
7286
|
return;
|
|
6451
7287
|
}
|
|
6452
7288
|
if (session.inspect && !isEditable && !e.repeat && !e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey) {
|
|
6453
|
-
const button = { x: xrayBtn, r: rulersBtn, g: gridBtn, p: pipetteBtn, m: motionBtn }[e.key.toLowerCase()];
|
|
7289
|
+
const button = { x: xrayBtn, r: rulersBtn, g: gridBtn, p: pipetteBtn, t: typographyBtn, m: motionBtn }[e.key.toLowerCase()];
|
|
6454
7290
|
if (button) { e.preventDefault(); button.click(); return; }
|
|
6455
7291
|
}
|
|
6456
7292
|
|