@goflowstate/widget-sdk 0.3.1 → 0.3.4
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 +24 -2
- package/dist/canvas-widget-sdk.js +534 -28
- package/dist/canvas-widget-sdk.js.map +3 -3
- package/dist/feedback.d.ts +81 -1
- package/dist/feedback.d.ts.map +1 -1
- package/dist/index.mjs +534 -28
- package/dist/index.mjs.map +3 -3
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @goflowstate/widget-sdk v0.3.
|
|
1
|
+
// @goflowstate/widget-sdk v0.3.4
|
|
2
2
|
// https://github.com/goflowstate/goflowstate-sdk
|
|
3
3
|
|
|
4
4
|
"use strict";
|
|
@@ -159,6 +159,46 @@ var CanvasWidgetSDK = (() => {
|
|
|
159
159
|
});
|
|
160
160
|
return best;
|
|
161
161
|
};
|
|
162
|
+
var TEXT_SAMPLE_LIMIT = 600;
|
|
163
|
+
var DISPLAY_MIN_PX = 22;
|
|
164
|
+
var sizeOf = (style) => {
|
|
165
|
+
const n = parseFloat(style.fontSize);
|
|
166
|
+
return Number.isFinite(n) && n > 0 ? n : 16;
|
|
167
|
+
};
|
|
168
|
+
var textSamples = (doc) => {
|
|
169
|
+
const out = [];
|
|
170
|
+
const all = doc.body.querySelectorAll("*");
|
|
171
|
+
for (let i = 0; i < all.length && out.length < TEXT_SAMPLE_LIMIT; i += 1) {
|
|
172
|
+
const el = all[i];
|
|
173
|
+
if (el.tagName === "SCRIPT" || el.tagName === "STYLE" || inOurs(el)) continue;
|
|
174
|
+
let chars = 0;
|
|
175
|
+
for (const node of Array.from(el.childNodes)) {
|
|
176
|
+
if (node.nodeType === 3) chars += (node.textContent ?? "").trim().length;
|
|
177
|
+
}
|
|
178
|
+
if (chars === 0 || !visible(el)) continue;
|
|
179
|
+
const style = styleOf(el);
|
|
180
|
+
if (!style.fontFamily) continue;
|
|
181
|
+
out.push({
|
|
182
|
+
family: style.fontFamily,
|
|
183
|
+
size: sizeOf(style),
|
|
184
|
+
chars,
|
|
185
|
+
style,
|
|
186
|
+
heading: /^H[1-3]$/.test(el.tagName)
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
return out;
|
|
190
|
+
};
|
|
191
|
+
var trackingEm = (style, min, max, fallback) => {
|
|
192
|
+
const px = style ? parseFloat(style.letterSpacing) : NaN;
|
|
193
|
+
const em = style && Number.isFinite(px) ? px / sizeOf(style) : fallback;
|
|
194
|
+
const held = Math.min(max, Math.max(min, em));
|
|
195
|
+
return held === 0 ? "0em" : `${Math.round(held * 1e3) / 1e3}em`;
|
|
196
|
+
};
|
|
197
|
+
var weightIn = (style, min, max, fallback) => {
|
|
198
|
+
const raw = style?.fontWeight ?? "";
|
|
199
|
+
const n = raw === "bold" ? 700 : raw === "normal" ? 400 : parseFloat(raw);
|
|
200
|
+
return Number.isFinite(n) ? String(Math.min(max, Math.max(min, n))) : fallback;
|
|
201
|
+
};
|
|
162
202
|
function readFlowTheme(doc = document) {
|
|
163
203
|
const body = doc.body;
|
|
164
204
|
const stage = body.querySelector(":scope > :not(script):not(style):not([data-fs-feedback-host])");
|
|
@@ -181,12 +221,32 @@ var CanvasWidgetSDK = (() => {
|
|
|
181
221
|
const pinnedCtaInk = parseColor(rootVar(doc, ["--fs-feedback-cta-ink"]));
|
|
182
222
|
const sampledCtaInk = !pinnedCta && sampledCta && buttonStyle ? parseColor(buttonStyle.color) : null;
|
|
183
223
|
const ctaInk = pinnedCtaInk ?? (sampledCtaInk && contrast(sampledCtaInk, cta) >= 3 ? sampledCtaInk : null) ?? (luminance(cta) > 0.4 ? BLACK : WHITE);
|
|
184
|
-
const
|
|
224
|
+
const samples = textSamples(doc);
|
|
225
|
+
const headings = Array.from(doc.querySelectorAll('h1, h2, [class*="title"]')).filter(
|
|
185
226
|
(el) => !inOurs(el) && visible(el)
|
|
186
227
|
);
|
|
187
|
-
const
|
|
188
|
-
|
|
228
|
+
const largest = (items, size) => items.reduce(
|
|
229
|
+
(best, item) => best === null || size(item) > size(best) ? item : best,
|
|
230
|
+
null
|
|
231
|
+
);
|
|
232
|
+
const bigHeading = largest(
|
|
233
|
+
headings.filter((el) => sizeOf(styleOf(el)) >= DISPLAY_MIN_PX),
|
|
234
|
+
(el) => sizeOf(styleOf(el))
|
|
235
|
+
);
|
|
236
|
+
const bigLine = largest(
|
|
237
|
+
samples.filter((t) => t.size >= DISPLAY_MIN_PX && t.chars >= 3),
|
|
238
|
+
(t) => t.size
|
|
239
|
+
);
|
|
240
|
+
const headingStyle = bigHeading ? styleOf(bigHeading) : bigLine?.style ?? (headings[0] ? styleOf(headings[0]) : null);
|
|
241
|
+
const byFamily = /* @__PURE__ */ new Map();
|
|
242
|
+
for (const t of samples) {
|
|
243
|
+
if (t.size < DISPLAY_MIN_PX && !t.heading)
|
|
244
|
+
byFamily.set(t.family, (byFamily.get(t.family) ?? 0) + t.chars);
|
|
245
|
+
}
|
|
246
|
+
const readBody = largest(Array.from(byFamily), ([, chars]) => chars)?.[0] ?? "";
|
|
247
|
+
const fontBody = rootVar(doc, ["--fs-feedback-font"]) || readBody || (stage ? styleOf(stage).fontFamily : "") || bodyStyle.fontFamily || rootVar(doc, ["--sb-font-body", "--font-body"]) || "system-ui, sans-serif";
|
|
189
248
|
const fontDisplay = rootVar(doc, ["--fs-feedback-font-display"]) || headingStyle?.fontFamily || rootVar(doc, ["--sb-font-display", "--sb-font-title", "--font-display"]) || fontBody;
|
|
249
|
+
const buttonType = buttonStyle?.fontFamily ? buttonStyle : null;
|
|
190
250
|
const field = Array.from(
|
|
191
251
|
doc.querySelectorAll('textarea, input[type="text"], input:not([type])')
|
|
192
252
|
).find((el) => !inOurs(el) && visible(el));
|
|
@@ -210,6 +270,11 @@ var CanvasWidgetSDK = (() => {
|
|
|
210
270
|
fontBody,
|
|
211
271
|
fontDisplay,
|
|
212
272
|
displayWeight: headingStyle?.fontWeight || "500",
|
|
273
|
+
displayTracking: trackingEm(headingStyle, -0.04, 0.02, -0.01),
|
|
274
|
+
fontButton: buttonType?.fontFamily || fontBody,
|
|
275
|
+
buttonWeight: weightIn(buttonType, 500, 800, "600"),
|
|
276
|
+
buttonTracking: trackingEm(buttonType, -0.02, 0.08, 0),
|
|
277
|
+
buttonTransform: buttonType?.textTransform === "uppercase" ? "uppercase" : "none",
|
|
213
278
|
radius,
|
|
214
279
|
buttonRadius: buttonRadius === null ? "999px" : `${Math.min(buttonRadius, 999)}px`,
|
|
215
280
|
dark
|
|
@@ -235,9 +300,10 @@ var CanvasWidgetSDK = (() => {
|
|
|
235
300
|
return null;
|
|
236
301
|
}
|
|
237
302
|
async function captureViewport(ground) {
|
|
238
|
-
const { domToCanvas: domToCanvas2 } = await Promise.resolve().then(() => (init_modern_screenshot(), modern_screenshot_exports));
|
|
239
303
|
const width = window.innerWidth;
|
|
240
304
|
const height = window.innerHeight;
|
|
305
|
+
const { scrollX, scrollY } = window;
|
|
306
|
+
const { domToCanvas: domToCanvas2 } = await Promise.resolve().then(() => (init_modern_screenshot(), modern_screenshot_exports));
|
|
241
307
|
const scale = Math.min(
|
|
242
308
|
window.devicePixelRatio || 1,
|
|
243
309
|
SCREENSHOT_MAX_SIDE / Math.max(width, height)
|
|
@@ -247,7 +313,7 @@ var CanvasWidgetSDK = (() => {
|
|
|
247
313
|
height,
|
|
248
314
|
scale,
|
|
249
315
|
backgroundColor: ground,
|
|
250
|
-
style: { transform: `translate(${-
|
|
316
|
+
style: { transform: `translate(${-scrollX}px, ${-scrollY}px)` },
|
|
251
317
|
filter: (node) => !(node instanceof Element && node.hasAttribute(FEEDBACK_HOST_ATTR))
|
|
252
318
|
});
|
|
253
319
|
return encodeScreenshot(canvas);
|
|
@@ -265,21 +331,168 @@ var CanvasWidgetSDK = (() => {
|
|
|
265
331
|
}
|
|
266
332
|
);
|
|
267
333
|
});
|
|
334
|
+
var PLACEMENT_BASE = 16;
|
|
335
|
+
var PLACEMENT_STEP = 8;
|
|
336
|
+
var PLACEMENT_PAD = 8;
|
|
337
|
+
var PLACEMENT_MAX_SHARE = 0.6;
|
|
338
|
+
var PLACEMENT_READ_COST = 240;
|
|
339
|
+
var EMPTY = 0;
|
|
340
|
+
var READ = 1;
|
|
341
|
+
var TAP = 2;
|
|
342
|
+
var SAMPLES = 5;
|
|
343
|
+
function choosePlacement(box, viewportHeight, at) {
|
|
344
|
+
const max = Math.max(PLACEMENT_BASE, viewportHeight * PLACEMENT_MAX_SHARE);
|
|
345
|
+
const span = box.size + PLACEMENT_PAD * 2;
|
|
346
|
+
const offsets = Array.from({ length: SAMPLES }, (_, i) => span * i / (SAMPLES - 1));
|
|
347
|
+
const x0 = box.left - PLACEMENT_PAD;
|
|
348
|
+
let best = PLACEMENT_BASE;
|
|
349
|
+
let bestCost = Infinity;
|
|
350
|
+
for (let bottom = PLACEMENT_BASE; bottom <= max; bottom += PLACEMENT_STEP) {
|
|
351
|
+
if (bottom >= bestCost) break;
|
|
352
|
+
const y0 = box.top - (bottom - box.bottom) - PLACEMENT_PAD;
|
|
353
|
+
let worst = EMPTY;
|
|
354
|
+
for (const dy of offsets) {
|
|
355
|
+
for (const dx of offsets) {
|
|
356
|
+
const found = at(x0 + dx, y0 + dy);
|
|
357
|
+
if (found > worst) worst = found;
|
|
358
|
+
if (worst === TAP) break;
|
|
359
|
+
}
|
|
360
|
+
if (worst === TAP) break;
|
|
361
|
+
}
|
|
362
|
+
if (worst === TAP) continue;
|
|
363
|
+
const cost = bottom + (worst === READ ? PLACEMENT_READ_COST : 0);
|
|
364
|
+
if (cost < bestCost) {
|
|
365
|
+
bestCost = cost;
|
|
366
|
+
best = bottom;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return best;
|
|
370
|
+
}
|
|
371
|
+
var TAPPABLE = 'a[href],button,input,select,textarea,summary,label,[role="button"],[role="link"],[role="tab"],[role="switch"],[role="checkbox"],[role="radio"],[role="slider"],[role="option"],[tabindex]:not([tabindex="-1"]),[contenteditable=""],[contenteditable="true"],[onclick]';
|
|
372
|
+
var PICTURE = "img,svg,canvas,video,picture";
|
|
373
|
+
var SURFACE_SHARE = 0.8;
|
|
374
|
+
function occupancyReader(host, doc = document) {
|
|
375
|
+
if (typeof doc.elementsFromPoint !== "function") return () => EMPTY;
|
|
376
|
+
const view = doc.defaultView;
|
|
377
|
+
const width = view?.innerWidth ?? 1;
|
|
378
|
+
const height = view?.innerHeight ?? 1;
|
|
379
|
+
const seen = /* @__PURE__ */ new Map();
|
|
380
|
+
const read = (el) => {
|
|
381
|
+
const known = seen.get(el);
|
|
382
|
+
if (known) return known;
|
|
383
|
+
const style = view ? view.getComputedStyle(el) : null;
|
|
384
|
+
let tap = el.matches(TAPPABLE) || style?.cursor === "pointer";
|
|
385
|
+
if (tap) {
|
|
386
|
+
const r = el.getBoundingClientRect();
|
|
387
|
+
if (r.width * r.height >= width * height * SURFACE_SHARE) tap = false;
|
|
388
|
+
}
|
|
389
|
+
const text = [];
|
|
390
|
+
if (typeof doc.createRange === "function") {
|
|
391
|
+
for (const node of Array.from(el.childNodes)) {
|
|
392
|
+
if (node.nodeType !== 3 || !node.nodeValue || !node.nodeValue.trim()) continue;
|
|
393
|
+
const range = doc.createRange();
|
|
394
|
+
range.selectNodeContents(node);
|
|
395
|
+
if (typeof range.getClientRects === "function")
|
|
396
|
+
text.push(...Array.from(range.getClientRects()));
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
const ground = parseColor(style?.backgroundColor);
|
|
400
|
+
const image = style?.backgroundImage ?? "";
|
|
401
|
+
const reading = {
|
|
402
|
+
tap,
|
|
403
|
+
picture: el.matches(PICTURE) || el.closest("svg") !== null,
|
|
404
|
+
text,
|
|
405
|
+
opaque: ground !== null && ground[3] >= 0.5 || image !== "" && image !== "none"
|
|
406
|
+
};
|
|
407
|
+
seen.set(el, reading);
|
|
408
|
+
return reading;
|
|
409
|
+
};
|
|
410
|
+
return (x, y) => {
|
|
411
|
+
const cx = Math.min(Math.max(x, 0), width - 1);
|
|
412
|
+
const cy = Math.min(Math.max(y, 0), height - 1);
|
|
413
|
+
const stack = doc.elementsFromPoint(cx, cy).filter((el) => el !== host && el !== doc.documentElement && el !== doc.body);
|
|
414
|
+
for (let el = stack[0] ?? null; el && el !== doc.body; el = el.parentElement) {
|
|
415
|
+
if (read(el).tap) return TAP;
|
|
416
|
+
}
|
|
417
|
+
for (const el of stack) {
|
|
418
|
+
const r = read(el);
|
|
419
|
+
if (r.picture || r.text.some((t) => cx >= t.left && cx <= t.right && cy >= t.top && cy <= t.bottom))
|
|
420
|
+
return READ;
|
|
421
|
+
if (r.opaque) break;
|
|
422
|
+
}
|
|
423
|
+
return EMPTY;
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
var BUTTON_SIZE = 44;
|
|
427
|
+
var TAB_WIDTH = 26;
|
|
428
|
+
var TAB_HEIGHT = 54;
|
|
429
|
+
var EDGE_MARGIN = 12;
|
|
430
|
+
var DOCK_PAST = 14;
|
|
431
|
+
var SPOT_KEY = "fs-feedback-spot";
|
|
432
|
+
var projectMomentum = (v, deceleration = 0.992) => v / 1e3 * deceleration / (1 - deceleration);
|
|
433
|
+
var rubberband = (overshoot, dimension, constant = 0.55) => overshoot * dimension * constant / (dimension + constant * Math.abs(overshoot));
|
|
434
|
+
var clampY = (y, height, view) => Math.min(
|
|
435
|
+
Math.max(y, view.top + EDGE_MARGIN),
|
|
436
|
+
Math.max(view.top + EDGE_MARGIN, view.height - view.bottom - EDGE_MARGIN - height)
|
|
437
|
+
);
|
|
438
|
+
function restingSpot(state, left, y, view) {
|
|
439
|
+
if (state === "float") {
|
|
440
|
+
return {
|
|
441
|
+
state,
|
|
442
|
+
x: left ? EDGE_MARGIN : view.width - BUTTON_SIZE - EDGE_MARGIN,
|
|
443
|
+
y: clampY(y, BUTTON_SIZE, view)
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
return { state, x: left ? 0 : view.width - TAB_WIDTH, y: clampY(y, TAB_HEIGHT, view) };
|
|
447
|
+
}
|
|
448
|
+
function settleRelease(from, velocity, view) {
|
|
449
|
+
const py = from.y + projectMomentum(velocity.y);
|
|
450
|
+
if (from.state !== "float") return restingSpot(from.state, from.state === "dock-left", py, view);
|
|
451
|
+
const px = from.x + projectMomentum(velocity.x);
|
|
452
|
+
const maxX = view.width - BUTTON_SIZE;
|
|
453
|
+
const tabY = py + (BUTTON_SIZE - TAB_HEIGHT) / 2;
|
|
454
|
+
if (px < -DOCK_PAST || from.x < 3) return restingSpot("dock-left", true, tabY, view);
|
|
455
|
+
if (px > maxX + DOCK_PAST || from.x > maxX - 3)
|
|
456
|
+
return restingSpot("dock-right", false, tabY, view);
|
|
457
|
+
return restingSpot("float", px + BUTTON_SIZE / 2 < view.width / 2, py, view);
|
|
458
|
+
}
|
|
268
459
|
var BUG_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="8" y="7" width="8" height="12" rx="4"/><path d="M9.5 7a2.5 2.5 0 0 1 5 0M12 11v8M4 13h4M16 13h4M5 7l3.2 2.4M19 7l-3.2 2.4M5 20l3.2-2.6M19 20l-3.2-2.6"/></svg>';
|
|
269
460
|
var STYLE = `
|
|
270
461
|
:host { all: initial; }
|
|
271
462
|
* { box-sizing: border-box; }
|
|
272
463
|
.bug {
|
|
273
|
-
position: fixed; z-index: 2147483000;
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
width: 44px; height: 44px; border-radius: 50%; padding: 0;
|
|
277
|
-
display: grid; place-items: center; cursor: pointer;
|
|
464
|
+
position: fixed; z-index: 2147483000; left: 0; top: 0;
|
|
465
|
+
width: 44px; height: 44px; border-radius: 22px; padding: 0; margin: 0;
|
|
466
|
+
display: var(--fs-feedback-display, grid); place-items: center; cursor: grab;
|
|
278
467
|
background: var(--surface); color: var(--ink); border: 1px solid var(--line);
|
|
279
|
-
box-shadow: 0
|
|
280
|
-
-webkit-tap-highlight-color: transparent;
|
|
468
|
+
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.2), 0 2px 5px rgba(0, 0, 0, 0.12);
|
|
469
|
+
-webkit-tap-highlight-color: transparent; touch-action: none; user-select: none; -webkit-user-select: none;
|
|
470
|
+
will-change: transform;
|
|
471
|
+
}
|
|
472
|
+
@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
|
|
473
|
+
.bug {
|
|
474
|
+
background: color-mix(in srgb, var(--surface) 74%, transparent);
|
|
475
|
+
-webkit-backdrop-filter: blur(14px) saturate(160%); backdrop-filter: blur(14px) saturate(160%);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
.bug svg { width: 20px; height: 20px; pointer-events: none; }
|
|
479
|
+
.bug.down svg { transform: scale(0.9); }
|
|
480
|
+
.bug.dragging { cursor: grabbing; box-shadow: 0 18px 36px rgba(0, 0, 0, 0.26), 0 3px 8px rgba(0, 0, 0, 0.14); }
|
|
481
|
+
.bug[data-state^="dock"] { width: 26px; height: 54px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.14); opacity: 0.92; }
|
|
482
|
+
.bug[data-state="dock-right"] { border-radius: 16px 0 0 16px; border-right-width: 0; }
|
|
483
|
+
.bug[data-state="dock-left"] { border-radius: 0 16px 16px 0; border-left-width: 0; }
|
|
484
|
+
.bug[data-state^="dock"] svg { width: 16px; height: 16px; }
|
|
485
|
+
.probe {
|
|
486
|
+
position: fixed; visibility: hidden; pointer-events: none; width: 0; height: 0;
|
|
487
|
+
padding: env(safe-area-inset-top, 0px) 0 env(safe-area-inset-bottom, 0px) 0;
|
|
488
|
+
}
|
|
489
|
+
.tuck { align-self: center; font-size: 13px; color: var(--ink-dim); }
|
|
490
|
+
@media (prefers-reduced-transparency: reduce) {
|
|
491
|
+
.bug { background: var(--surface); -webkit-backdrop-filter: none; backdrop-filter: none; }
|
|
492
|
+
}
|
|
493
|
+
@media (prefers-contrast: more) {
|
|
494
|
+
.bug { background: var(--surface); border-color: var(--ink); -webkit-backdrop-filter: none; backdrop-filter: none; }
|
|
281
495
|
}
|
|
282
|
-
.bug svg { width: 20px; height: 20px; }
|
|
283
496
|
.bug:focus-visible, .send:focus-visible, .link:focus-visible, textarea:focus-visible {
|
|
284
497
|
outline: 2px solid var(--ink); outline-offset: 2px;
|
|
285
498
|
}
|
|
@@ -294,9 +507,15 @@ var CanvasWidgetSDK = (() => {
|
|
|
294
507
|
font-family: var(--font-body); font-size: 15px; line-height: 1.4;
|
|
295
508
|
}
|
|
296
509
|
.grab { width: 36px; height: 4px; border-radius: 2px; background: var(--line); align-self: center; }
|
|
297
|
-
h2 {
|
|
510
|
+
h2 {
|
|
511
|
+
margin: 2px 0 0; font-family: var(--font-display); font-weight: var(--display-weight);
|
|
512
|
+
font-size: 22px; line-height: 1.15; letter-spacing: var(--display-tracking); text-wrap: balance;
|
|
513
|
+
}
|
|
298
514
|
.shot { display: flex; gap: 12px; align-items: center; font-size: 13px; color: var(--ink-dim); }
|
|
299
515
|
.shot img { width: 50px; max-height: 108px; object-fit: cover; object-position: top; border-radius: 8px; border: 1px solid var(--line); }
|
|
516
|
+
.ghost { display: none; width: 50px; height: 108px; border-radius: 8px; border: 1px solid var(--line); background: var(--fill); }
|
|
517
|
+
.shot[data-pending] .ghost { display: block; }
|
|
518
|
+
.shot[data-pending] img { display: none; }
|
|
300
519
|
.link { background: none; border: 0; padding: 0; font: inherit; color: var(--ink); text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
|
|
301
520
|
textarea {
|
|
302
521
|
width: 100%; min-height: 84px; resize: none; font: inherit; font-size: 16px; color: var(--ink);
|
|
@@ -304,19 +523,29 @@ textarea {
|
|
|
304
523
|
}
|
|
305
524
|
textarea::placeholder { color: var(--ink-dim); }
|
|
306
525
|
.send {
|
|
307
|
-
font:
|
|
526
|
+
font-family: var(--font-button); font-weight: var(--button-weight); font-size: 17px; line-height: 1.2;
|
|
527
|
+
letter-spacing: var(--button-tracking); text-transform: var(--button-transform);
|
|
528
|
+
cursor: pointer; border: 0; padding: 15px 14px;
|
|
308
529
|
background: var(--cta); color: var(--cta-ink); border-radius: var(--button-radius);
|
|
309
530
|
}
|
|
310
531
|
.send[disabled] { opacity: 0.5; cursor: default; }
|
|
311
532
|
.status { margin: 0; font-size: 13px; color: var(--ink-dim); min-height: 1.4em; }
|
|
312
533
|
[hidden] { display: none !important; }
|
|
313
534
|
@media (prefers-reduced-motion: no-preference) {
|
|
535
|
+
.bug {
|
|
536
|
+
transition: width 0.22s cubic-bezier(0.2, 0.8, 0.2, 1), height 0.22s cubic-bezier(0.2, 0.8, 0.2, 1),
|
|
537
|
+
border-radius 0.22s cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 0.22s ease;
|
|
538
|
+
}
|
|
539
|
+
.bug svg { transition: transform 0.1s ease-out, width 0.22s ease, height 0.22s ease; }
|
|
314
540
|
.sheet { animation: rise 0.22s ease-out; }
|
|
541
|
+
.shot[data-pending] .ghost { animation: wait 1.1s ease-in-out infinite alternate; }
|
|
542
|
+
@keyframes wait { from { opacity: 0.45; } to { opacity: 1; } }
|
|
315
543
|
@keyframes rise { from { transform: translateY(24px); opacity: 0; } to { transform: none; opacity: 1; } }
|
|
316
544
|
}
|
|
317
545
|
`;
|
|
318
546
|
var mounted = null;
|
|
319
547
|
var feedbackOptedOut = (doc = document) => doc.documentElement.getAttribute("data-fs-feedback") === "off";
|
|
548
|
+
var SCROLL_OPTIONS = { capture: true, passive: true };
|
|
320
549
|
function mountFlowFeedback(options) {
|
|
321
550
|
if (typeof document === "undefined" || !document.body) return null;
|
|
322
551
|
if (mounted) return mounted;
|
|
@@ -325,15 +554,17 @@ textarea::placeholder { color: var(--ink-dim); }
|
|
|
325
554
|
host.setAttribute(FEEDBACK_HOST_ATTR, "");
|
|
326
555
|
const root = host.attachShadow({ mode: "open" });
|
|
327
556
|
root.innerHTML = `<style>${STYLE}</style>
|
|
328
|
-
<button type="button" class="bug" aria-label="Send feedback on this page">${BUG_ICON}</button>
|
|
557
|
+
<button type="button" class="bug" data-state="float" aria-label="Send feedback on this page">${BUG_ICON}</button>
|
|
558
|
+
<div class="probe" aria-hidden="true"></div>
|
|
329
559
|
<div class="scrim" hidden></div>
|
|
330
560
|
<div class="sheet" role="dialog" aria-modal="true" aria-labelledby="fsf-title" hidden>
|
|
331
561
|
<div class="grab" aria-hidden="true"></div>
|
|
332
562
|
<h2 id="fsf-title">Feedback on this page</h2>
|
|
333
|
-
<div class="shot" hidden><img alt="Screenshot of this page" /><span><span>Screenshot attached</span><br /><button type="button" class="link remove">Remove screenshot</button></span></div>
|
|
563
|
+
<div class="shot" hidden><span class="ghost" aria-hidden="true"></span><img alt="Screenshot of this page" /><span><span class="shot-label">Screenshot attached</span><br /><button type="button" class="link remove">Remove screenshot</button></span></div>
|
|
334
564
|
<textarea aria-labelledby="fsf-title" placeholder="What should change?" maxlength="2000"></textarea>
|
|
335
565
|
<p class="status" role="status" aria-live="polite"></p>
|
|
336
566
|
<button type="button" class="send" disabled>Send feedback</button>
|
|
567
|
+
<button type="button" class="link tuck">Tuck the button to the side</button>
|
|
337
568
|
</div>`;
|
|
338
569
|
const q = (selector) => root.querySelector(selector);
|
|
339
570
|
const bug = q(".bug");
|
|
@@ -341,14 +572,204 @@ textarea::placeholder { color: var(--ink-dim); }
|
|
|
341
572
|
const sheet = q(".sheet");
|
|
342
573
|
const shot = q(".shot");
|
|
343
574
|
const img = q(".shot img");
|
|
575
|
+
const shotLabel = q(".shot-label");
|
|
344
576
|
const remove = q(".remove");
|
|
345
577
|
const text = q("textarea");
|
|
346
578
|
const status = q(".status");
|
|
347
579
|
const send = q(".send");
|
|
580
|
+
const tuck = q(".tuck");
|
|
581
|
+
const probe = q(".probe");
|
|
348
582
|
let theme = readFlowTheme();
|
|
349
583
|
let screenshot = null;
|
|
350
584
|
let busy = false;
|
|
351
585
|
let closeTimer = null;
|
|
586
|
+
let placeTimer = null;
|
|
587
|
+
let autoBottom = PLACEMENT_BASE;
|
|
588
|
+
let spot = { state: "float", x: 0, y: 0 };
|
|
589
|
+
let placedByPerson = false;
|
|
590
|
+
let velocity = { x: 0, y: 0 };
|
|
591
|
+
let frame = 0;
|
|
592
|
+
const px = (name) => {
|
|
593
|
+
const raw = getComputedStyle(host).getPropertyValue(name).trim();
|
|
594
|
+
const n = raw ? parseFloat(raw) : NaN;
|
|
595
|
+
return Number.isFinite(n) ? n : null;
|
|
596
|
+
};
|
|
597
|
+
const viewport = () => {
|
|
598
|
+
const style = getComputedStyle(probe);
|
|
599
|
+
return {
|
|
600
|
+
width: window.innerWidth,
|
|
601
|
+
height: window.innerHeight,
|
|
602
|
+
top: parseFloat(style.paddingTop) || 0,
|
|
603
|
+
bottom: parseFloat(style.paddingBottom) || 0
|
|
604
|
+
};
|
|
605
|
+
};
|
|
606
|
+
const draw = () => {
|
|
607
|
+
bug.style.transform = `translate3d(${spot.x}px, ${spot.y}px, 0)`;
|
|
608
|
+
};
|
|
609
|
+
const setState = (next) => {
|
|
610
|
+
spot.state = next;
|
|
611
|
+
bug.dataset.state = next;
|
|
612
|
+
};
|
|
613
|
+
const remember = (spot2) => {
|
|
614
|
+
if (!placedByPerson) return;
|
|
615
|
+
try {
|
|
616
|
+
const view = viewport();
|
|
617
|
+
const width = spot2.state === "float" ? BUTTON_SIZE : TAB_WIDTH;
|
|
618
|
+
localStorage.setItem(
|
|
619
|
+
SPOT_KEY,
|
|
620
|
+
JSON.stringify({
|
|
621
|
+
state: spot2.state,
|
|
622
|
+
left: spot2.x + width / 2 < view.width / 2,
|
|
623
|
+
y: view.height > 0 ? spot2.y / view.height : 0
|
|
624
|
+
})
|
|
625
|
+
);
|
|
626
|
+
} catch {
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
const still = () => typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
630
|
+
const springTo = (to, flicked) => {
|
|
631
|
+
cancelAnimationFrame(frame);
|
|
632
|
+
frame = 0;
|
|
633
|
+
setState(to.state);
|
|
634
|
+
remember(to);
|
|
635
|
+
if (still() || typeof requestAnimationFrame !== "function") {
|
|
636
|
+
spot = { ...to };
|
|
637
|
+
velocity = { x: 0, y: 0 };
|
|
638
|
+
draw();
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
const omega = 2 * Math.PI / 0.4;
|
|
642
|
+
const k = omega * omega;
|
|
643
|
+
const c = 2 * (flicked ? 0.8 : 1) * omega;
|
|
644
|
+
let last = performance.now();
|
|
645
|
+
const tick = (now) => {
|
|
646
|
+
const dt = Math.min(0.032, (now - last) / 1e3);
|
|
647
|
+
last = now;
|
|
648
|
+
velocity.x += (-k * (spot.x - to.x) - c * velocity.x) * dt;
|
|
649
|
+
velocity.y += (-k * (spot.y - to.y) - c * velocity.y) * dt;
|
|
650
|
+
spot.x += velocity.x * dt;
|
|
651
|
+
spot.y += velocity.y * dt;
|
|
652
|
+
const close2 = Math.abs(spot.x - to.x) < 0.3 && Math.abs(spot.y - to.y) < 0.3 && Math.hypot(velocity.x, velocity.y) < 6;
|
|
653
|
+
if (close2) {
|
|
654
|
+
spot = { ...to };
|
|
655
|
+
velocity = { x: 0, y: 0 };
|
|
656
|
+
frame = 0;
|
|
657
|
+
draw();
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
draw();
|
|
661
|
+
frame = requestAnimationFrame(tick);
|
|
662
|
+
};
|
|
663
|
+
frame = requestAnimationFrame(tick);
|
|
664
|
+
};
|
|
665
|
+
const place = () => {
|
|
666
|
+
placeTimer = null;
|
|
667
|
+
if (!sheet.hidden || bug.hidden || drag || frame) return;
|
|
668
|
+
const view = viewport();
|
|
669
|
+
if (placedByPerson || spot.state !== "float") {
|
|
670
|
+
const width = spot.state === "float" ? BUTTON_SIZE : TAB_WIDTH;
|
|
671
|
+
spot = restingSpot(spot.state, spot.x + width / 2 < view.width / 2, spot.y, view);
|
|
672
|
+
draw();
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
const right = px("--fs-feedback-right") ?? 14;
|
|
676
|
+
const left = view.width - right - BUTTON_SIZE;
|
|
677
|
+
const pinned = px("--fs-feedback-bottom");
|
|
678
|
+
if (pinned === null && typeof document.elementsFromPoint === "function") {
|
|
679
|
+
autoBottom = choosePlacement(
|
|
680
|
+
{
|
|
681
|
+
left,
|
|
682
|
+
top: view.height - view.bottom - autoBottom - BUTTON_SIZE,
|
|
683
|
+
size: BUTTON_SIZE,
|
|
684
|
+
bottom: autoBottom
|
|
685
|
+
},
|
|
686
|
+
view.height,
|
|
687
|
+
occupancyReader(host)
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
const bottom = pinned ?? autoBottom;
|
|
691
|
+
spot = { state: "float", x: left, y: view.height - view.bottom - bottom - BUTTON_SIZE };
|
|
692
|
+
draw();
|
|
693
|
+
};
|
|
694
|
+
const schedulePlace = () => {
|
|
695
|
+
if (placeTimer) clearTimeout(placeTimer);
|
|
696
|
+
placeTimer = setTimeout(place, 150);
|
|
697
|
+
};
|
|
698
|
+
let drag = null;
|
|
699
|
+
let suppressClick = false;
|
|
700
|
+
const onDown = (event) => {
|
|
701
|
+
if (!sheet.hidden || busy) return;
|
|
702
|
+
bug.setPointerCapture?.(event.pointerId);
|
|
703
|
+
cancelAnimationFrame(frame);
|
|
704
|
+
frame = 0;
|
|
705
|
+
velocity = { x: 0, y: 0 };
|
|
706
|
+
drag = {
|
|
707
|
+
id: event.pointerId,
|
|
708
|
+
ox: event.clientX - spot.x,
|
|
709
|
+
oy: event.clientY - spot.y,
|
|
710
|
+
sx: event.clientX,
|
|
711
|
+
sy: event.clientY,
|
|
712
|
+
moved: false,
|
|
713
|
+
history: [{ t: event.timeStamp, x: event.clientX, y: event.clientY }]
|
|
714
|
+
};
|
|
715
|
+
bug.classList.add("down");
|
|
716
|
+
};
|
|
717
|
+
const onMove = (event) => {
|
|
718
|
+
if (!drag || event.pointerId !== drag.id) return;
|
|
719
|
+
const dx = event.clientX - drag.sx;
|
|
720
|
+
const dy = event.clientY - drag.sy;
|
|
721
|
+
if (!drag.moved && Math.hypot(dx, dy) < 8) return;
|
|
722
|
+
if (!drag.moved) {
|
|
723
|
+
drag.moved = true;
|
|
724
|
+
bug.classList.add("dragging");
|
|
725
|
+
}
|
|
726
|
+
drag.history.push({ t: event.timeStamp, x: event.clientX, y: event.clientY });
|
|
727
|
+
if (drag.history.length > 6) drag.history.shift();
|
|
728
|
+
const view = viewport();
|
|
729
|
+
if (spot.state !== "float") {
|
|
730
|
+
const inward = spot.state === "dock-left" ? dx : -dx;
|
|
731
|
+
if (inward <= 18) {
|
|
732
|
+
spot = restingSpot(spot.state, spot.state === "dock-left", event.clientY - drag.oy, view);
|
|
733
|
+
draw();
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
setState("float");
|
|
737
|
+
drag.ox = BUTTON_SIZE / 2;
|
|
738
|
+
drag.oy = BUTTON_SIZE / 2;
|
|
739
|
+
}
|
|
740
|
+
let x = event.clientX - drag.ox;
|
|
741
|
+
let y = event.clientY - drag.oy;
|
|
742
|
+
const maxX = view.width - BUTTON_SIZE;
|
|
743
|
+
const maxY = view.height - BUTTON_SIZE;
|
|
744
|
+
if (x < 0) x = rubberband(x, 60);
|
|
745
|
+
else if (x > maxX) x = maxX + rubberband(x - maxX, 60);
|
|
746
|
+
if (y < 0) y = rubberband(y, 80);
|
|
747
|
+
else if (y > maxY) y = maxY + rubberband(y - maxY, 80);
|
|
748
|
+
spot.x = x;
|
|
749
|
+
spot.y = y;
|
|
750
|
+
draw();
|
|
751
|
+
};
|
|
752
|
+
const onUp = (event) => {
|
|
753
|
+
if (!drag || event.pointerId !== drag.id) return;
|
|
754
|
+
const ended = drag;
|
|
755
|
+
drag = null;
|
|
756
|
+
bug.classList.remove("down", "dragging");
|
|
757
|
+
if (!ended.moved) return;
|
|
758
|
+
suppressClick = true;
|
|
759
|
+
setTimeout(() => {
|
|
760
|
+
suppressClick = false;
|
|
761
|
+
}, 0);
|
|
762
|
+
const first = ended.history[0];
|
|
763
|
+
const last = ended.history[ended.history.length - 1];
|
|
764
|
+
const dt = Math.max(1, last.t - first.t);
|
|
765
|
+
const paused = event.timeStamp - last.t > 90;
|
|
766
|
+
velocity = paused ? { x: 0, y: 0 } : { x: (last.x - first.x) / dt * 1e3, y: (last.y - first.y) / dt * 1e3 };
|
|
767
|
+
placedByPerson = true;
|
|
768
|
+
springTo(settleRelease(spot, velocity, viewport()), Math.hypot(velocity.x, velocity.y) > 350);
|
|
769
|
+
};
|
|
770
|
+
const observer = typeof MutationObserver === "function" ? new MutationObserver((records) => {
|
|
771
|
+
if (records.some((r) => r.target !== host)) schedulePlace();
|
|
772
|
+
}) : null;
|
|
352
773
|
const paint = () => {
|
|
353
774
|
theme = readFlowTheme();
|
|
354
775
|
const vars = {
|
|
@@ -362,6 +783,11 @@ textarea::placeholder { color: var(--ink-dim); }
|
|
|
362
783
|
"--font-body": theme.fontBody,
|
|
363
784
|
"--font-display": theme.fontDisplay,
|
|
364
785
|
"--display-weight": theme.displayWeight,
|
|
786
|
+
"--display-tracking": theme.displayTracking,
|
|
787
|
+
"--font-button": theme.fontButton,
|
|
788
|
+
"--button-weight": theme.buttonWeight,
|
|
789
|
+
"--button-tracking": theme.buttonTracking,
|
|
790
|
+
"--button-transform": theme.buttonTransform,
|
|
365
791
|
"--radius": theme.radius,
|
|
366
792
|
"--button-radius": theme.buttonRadius
|
|
367
793
|
};
|
|
@@ -371,15 +797,21 @@ textarea::placeholder { color: var(--ink-dim); }
|
|
|
371
797
|
const sync = () => {
|
|
372
798
|
send.disabled = busy || text.value.trim().length === 0;
|
|
373
799
|
};
|
|
800
|
+
let round = 0;
|
|
801
|
+
let pending = null;
|
|
374
802
|
const setShot = (url) => {
|
|
375
803
|
screenshot = url;
|
|
376
804
|
shot.hidden = url === null;
|
|
805
|
+
shot.removeAttribute("data-pending");
|
|
806
|
+
shotLabel.textContent = "Screenshot attached";
|
|
377
807
|
if (url) img.src = url;
|
|
378
808
|
else img.removeAttribute("src");
|
|
379
809
|
};
|
|
380
810
|
const close = () => {
|
|
381
811
|
if (closeTimer) clearTimeout(closeTimer);
|
|
382
812
|
closeTimer = null;
|
|
813
|
+
round += 1;
|
|
814
|
+
pending = null;
|
|
383
815
|
sheet.hidden = true;
|
|
384
816
|
scrim.hidden = true;
|
|
385
817
|
bug.hidden = false;
|
|
@@ -388,20 +820,30 @@ textarea::placeholder { color: var(--ink-dim); }
|
|
|
388
820
|
status.textContent = "";
|
|
389
821
|
busy = false;
|
|
390
822
|
sync();
|
|
823
|
+
schedulePlace();
|
|
391
824
|
};
|
|
392
|
-
const open =
|
|
393
|
-
if (!sheet.hidden
|
|
394
|
-
busy
|
|
825
|
+
const open = () => {
|
|
826
|
+
if (!sheet.hidden) return pending ?? Promise.resolve();
|
|
827
|
+
if (busy) return Promise.resolve();
|
|
395
828
|
paint();
|
|
829
|
+
const mine = round += 1;
|
|
396
830
|
bug.hidden = true;
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
831
|
+
setShot(null);
|
|
832
|
+
shot.hidden = false;
|
|
833
|
+
shot.setAttribute("data-pending", "");
|
|
834
|
+
shotLabel.textContent = "Taking a screenshot";
|
|
835
|
+
tuck.hidden = spot.state !== "float";
|
|
401
836
|
scrim.hidden = false;
|
|
402
837
|
sheet.hidden = false;
|
|
403
838
|
sync();
|
|
404
|
-
|
|
839
|
+
const capture = options.capture ?? (() => captureViewport(theme.ground));
|
|
840
|
+
pending = withTimeout(Promise.resolve().then(capture), CAPTURE_TIMEOUT_MS).then((url) => {
|
|
841
|
+
if (mine !== round) return;
|
|
842
|
+
pending = null;
|
|
843
|
+
setShot(url);
|
|
844
|
+
if (!root.activeElement || root.activeElement === bug) text.focus();
|
|
845
|
+
});
|
|
846
|
+
return pending;
|
|
405
847
|
};
|
|
406
848
|
const submit = async () => {
|
|
407
849
|
const message = text.value.trim();
|
|
@@ -409,6 +851,9 @@ textarea::placeholder { color: var(--ink-dim); }
|
|
|
409
851
|
busy = true;
|
|
410
852
|
sync();
|
|
411
853
|
status.textContent = "";
|
|
854
|
+
const mine = round;
|
|
855
|
+
if (pending) await pending;
|
|
856
|
+
if (mine !== round) return;
|
|
412
857
|
try {
|
|
413
858
|
await options.submit({
|
|
414
859
|
message,
|
|
@@ -428,9 +873,39 @@ textarea::placeholder { color: var(--ink-dim); }
|
|
|
428
873
|
sync();
|
|
429
874
|
}
|
|
430
875
|
};
|
|
431
|
-
bug.addEventListener("click", () =>
|
|
876
|
+
bug.addEventListener("click", () => {
|
|
877
|
+
if (!suppressClick) void open();
|
|
878
|
+
});
|
|
879
|
+
bug.addEventListener("pointerdown", onDown);
|
|
880
|
+
if (!options.capture)
|
|
881
|
+
bug.addEventListener("pointerdown", () => void Promise.resolve().then(() => (init_modern_screenshot(), modern_screenshot_exports)).catch(() => {
|
|
882
|
+
}), {
|
|
883
|
+
once: true
|
|
884
|
+
});
|
|
885
|
+
bug.addEventListener("pointermove", onMove);
|
|
886
|
+
bug.addEventListener("pointerup", onUp);
|
|
887
|
+
bug.addEventListener("pointercancel", onUp);
|
|
888
|
+
tuck.addEventListener("click", () => {
|
|
889
|
+
const view = viewport();
|
|
890
|
+
const left = spot.x + BUTTON_SIZE / 2 < view.width / 2;
|
|
891
|
+
close();
|
|
892
|
+
placedByPerson = true;
|
|
893
|
+
springTo(
|
|
894
|
+
restingSpot(
|
|
895
|
+
left ? "dock-left" : "dock-right",
|
|
896
|
+
left,
|
|
897
|
+
spot.y + (BUTTON_SIZE - TAB_HEIGHT) / 2,
|
|
898
|
+
view
|
|
899
|
+
),
|
|
900
|
+
false
|
|
901
|
+
);
|
|
902
|
+
});
|
|
432
903
|
scrim.addEventListener("click", close);
|
|
433
|
-
remove.addEventListener("click", () =>
|
|
904
|
+
remove.addEventListener("click", () => {
|
|
905
|
+
round += 1;
|
|
906
|
+
pending = null;
|
|
907
|
+
setShot(null);
|
|
908
|
+
});
|
|
434
909
|
text.addEventListener("input", sync);
|
|
435
910
|
send.addEventListener("click", () => void submit());
|
|
436
911
|
sheet.addEventListener("keydown", (event) => {
|
|
@@ -442,6 +917,29 @@ textarea::placeholder { color: var(--ink-dim); }
|
|
|
442
917
|
});
|
|
443
918
|
document.body.appendChild(host);
|
|
444
919
|
paint();
|
|
920
|
+
try {
|
|
921
|
+
const saved = JSON.parse(localStorage.getItem(SPOT_KEY) ?? "null");
|
|
922
|
+
if (saved && typeof saved.y === "number" && (saved.state === "float" || saved.state === "dock-left" || saved.state === "dock-right")) {
|
|
923
|
+
const view = viewport();
|
|
924
|
+
placedByPerson = true;
|
|
925
|
+
setState(saved.state);
|
|
926
|
+
spot = restingSpot(saved.state, saved.left === true, saved.y * view.height, view);
|
|
927
|
+
}
|
|
928
|
+
} catch {
|
|
929
|
+
}
|
|
930
|
+
place();
|
|
931
|
+
schedulePlace();
|
|
932
|
+
observer?.observe(document.body, {
|
|
933
|
+
childList: true,
|
|
934
|
+
subtree: true,
|
|
935
|
+
attributes: true,
|
|
936
|
+
attributeFilter: ["class", "style", "hidden"]
|
|
937
|
+
});
|
|
938
|
+
window.addEventListener("resize", schedulePlace);
|
|
939
|
+
window.addEventListener("load", schedulePlace);
|
|
940
|
+
window.addEventListener("scroll", schedulePlace, SCROLL_OPTIONS);
|
|
941
|
+
document.addEventListener("transitionend", schedulePlace, SCROLL_OPTIONS);
|
|
942
|
+
document.addEventListener("animationend", schedulePlace, SCROLL_OPTIONS);
|
|
445
943
|
options.onReady?.();
|
|
446
944
|
mounted = {
|
|
447
945
|
host,
|
|
@@ -452,6 +950,14 @@ textarea::placeholder { color: var(--ink-dim); }
|
|
|
452
950
|
close,
|
|
453
951
|
unmount: () => {
|
|
454
952
|
if (closeTimer) clearTimeout(closeTimer);
|
|
953
|
+
if (placeTimer) clearTimeout(placeTimer);
|
|
954
|
+
cancelAnimationFrame(frame);
|
|
955
|
+
observer?.disconnect();
|
|
956
|
+
window.removeEventListener("resize", schedulePlace);
|
|
957
|
+
window.removeEventListener("load", schedulePlace);
|
|
958
|
+
window.removeEventListener("scroll", schedulePlace, SCROLL_OPTIONS);
|
|
959
|
+
document.removeEventListener("transitionend", schedulePlace, SCROLL_OPTIONS);
|
|
960
|
+
document.removeEventListener("animationend", schedulePlace, SCROLL_OPTIONS);
|
|
455
961
|
host.remove();
|
|
456
962
|
mounted = null;
|
|
457
963
|
}
|