@goflowstate/widget-sdk 0.3.1 → 0.3.2
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 +15 -0
- package/dist/canvas-widget-sdk.js +409 -11
- package/dist/canvas-widget-sdk.js.map +3 -3
- package/dist/feedback.d.ts +73 -0
- package/dist/feedback.d.ts.map +1 -1
- package/dist/index.mjs +409 -11
- package/dist/index.mjs.map +3 -3
- package/package.json +1 -1
package/dist/feedback.d.ts
CHANGED
|
@@ -71,6 +71,79 @@ export declare const luminance: (c: Rgba) => number;
|
|
|
71
71
|
export declare function readFlowTheme(doc?: Document): FlowFeedbackTheme;
|
|
72
72
|
/** Step the JPEG quality, then the size, until the picture fits the service's cap. */
|
|
73
73
|
export declare function encodeScreenshot(canvas: HTMLCanvasElement): string | null;
|
|
74
|
+
export declare const PLACEMENT_BASE = 16;
|
|
75
|
+
export declare const PLACEMENT_STEP = 8;
|
|
76
|
+
/** Clear space kept around the icon, so it does not crowd what it sits beside. */
|
|
77
|
+
export declare const PLACEMENT_PAD = 8;
|
|
78
|
+
/** The icon never climbs past this share of the viewport. */
|
|
79
|
+
export declare const PLACEMENT_MAX_SHARE = 0.6;
|
|
80
|
+
/** How far up empty space is still worth more than sitting on a line of text lower down. */
|
|
81
|
+
export declare const PLACEMENT_READ_COST = 240;
|
|
82
|
+
/** What is at a point: nothing, something to read or look at, or something to tap. */
|
|
83
|
+
export declare const EMPTY = 0;
|
|
84
|
+
export declare const READ = 1;
|
|
85
|
+
export declare const TAP = 2;
|
|
86
|
+
export type Occupancy = typeof EMPTY | typeof READ | typeof TAP;
|
|
87
|
+
export interface PlacementBox {
|
|
88
|
+
/** The icon's box as measured now, in viewport pixels. */
|
|
89
|
+
left: number;
|
|
90
|
+
top: number;
|
|
91
|
+
size: number;
|
|
92
|
+
/** The bottom offset that box was measured at. */
|
|
93
|
+
bottom: number;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The bottom offset to sit at. `at(x, y)` says what is at a viewport point. An empty spot costs
|
|
97
|
+
* its height; a spot over text or a picture costs its height plus PLACEMENT_READ_COST; a spot
|
|
98
|
+
* over a control is taken only when there is no other, and then it is the base.
|
|
99
|
+
*/
|
|
100
|
+
export declare function choosePlacement(box: PlacementBox, viewportHeight: number, at: (x: number, y: number) => Occupancy): number;
|
|
101
|
+
/**
|
|
102
|
+
* Reads what is at viewport points, the feedback host aside. One reader serves one placement
|
|
103
|
+
* pass: what it learns about an element is kept for the pass and dropped with it.
|
|
104
|
+
*
|
|
105
|
+
* A tap lands on the topmost element and travels up its ancestors, so only those can make a
|
|
106
|
+
* point a control: a button under the flow's own bottom sheet is not one. What a person reads
|
|
107
|
+
* is whatever shows through, down to the first element that paints its own ground.
|
|
108
|
+
*/
|
|
109
|
+
export declare function occupancyReader(host: Element, doc?: Document): (x: number, y: number) => Occupancy;
|
|
110
|
+
export type DockState = 'float' | 'dock-left' | 'dock-right';
|
|
111
|
+
export declare const BUTTON_SIZE = 44;
|
|
112
|
+
export declare const TAB_WIDTH = 26;
|
|
113
|
+
export declare const TAB_HEIGHT = 54;
|
|
114
|
+
export declare const EDGE_MARGIN = 12;
|
|
115
|
+
/** How far past an edge a release has to be heading before the button docks there. */
|
|
116
|
+
export declare const DOCK_PAST = 14;
|
|
117
|
+
/** Storage key for where the person left the button. One origin serves every flow, so the
|
|
118
|
+
* choice follows them from flow to flow. */
|
|
119
|
+
export declare const SPOT_KEY = "fs-feedback-spot";
|
|
120
|
+
/** The part of the viewport the button may rest in: its size, less the safe-area insets. */
|
|
121
|
+
export interface FeedbackViewport {
|
|
122
|
+
width: number;
|
|
123
|
+
height: number;
|
|
124
|
+
top: number;
|
|
125
|
+
bottom: number;
|
|
126
|
+
}
|
|
127
|
+
export interface FeedbackSpot {
|
|
128
|
+
state: DockState;
|
|
129
|
+
x: number;
|
|
130
|
+
y: number;
|
|
131
|
+
}
|
|
132
|
+
/** Where a release at velocity `v` (px per second) comes to rest: scroll deceleration. */
|
|
133
|
+
export declare const projectMomentum: (v: number, deceleration?: number) => number;
|
|
134
|
+
/** Past a boundary the button follows less and less, so the edge reads as soft, not stuck. */
|
|
135
|
+
export declare const rubberband: (overshoot: number, dimension: number, constant?: number) => number;
|
|
136
|
+
/** The resting spot for a state on one side, at a height. */
|
|
137
|
+
export declare function restingSpot(state: DockState, left: boolean, y: number, view: FeedbackViewport): FeedbackSpot;
|
|
138
|
+
/**
|
|
139
|
+
* Where a drag that ends at (x, y) with velocity (vx, vy) settles. A floating button heading
|
|
140
|
+
* past an edge, or let go against one, docks on that side; otherwise it floats on the side its
|
|
141
|
+
* momentum carries it to. A docked tab stays docked and only slides along its edge.
|
|
142
|
+
*/
|
|
143
|
+
export declare function settleRelease(from: FeedbackSpot, velocity: {
|
|
144
|
+
x: number;
|
|
145
|
+
y: number;
|
|
146
|
+
}, view: FeedbackViewport): FeedbackSpot;
|
|
74
147
|
/** Whether the page asked for no feedback icon (`<html data-fs-feedback="off">`). */
|
|
75
148
|
export declare const feedbackOptedOut: (doc?: Document) => boolean;
|
|
76
149
|
export declare function mountFlowFeedback(options: FlowFeedbackOptions): FlowFeedbackHandle | null;
|
package/dist/feedback.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feedback.d.ts","sourceRoot":"","sources":["../src/feedback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7C;AAED,MAAM,WAAW,mBAAmB;IAClC,yFAAyF;IACzF,MAAM,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACzD;8DAC0D;IAC1D,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvC,qFAAqF;IACrF,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,8CAA8C;IAC9C,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,0EAA0E;IAC1E,OAAO,IAAI,IAAI,CAAC;IAChB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,KAAK,IAAI,IAAI,CAAC;IACd,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,eAAO,MAAM,kBAAkB,0BAA0B,CAAC;AAC1D,gGAAgG;AAChG,eAAO,MAAM,oBAAoB,SAAU,CAAC;AAC5C,eAAO,MAAM,mBAAmB,OAAO,CAAC;AAKxC,KAAK,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAE7C,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,GAAG,IAAI,CA8BtE;AAaD,+BAA+B;AAC/B,eAAO,MAAM,SAAS,GAAI,GAAG,IAAI,KAAG,MAMnC,CAAC;AAmDF;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,GAAE,QAAmB,GAAG,iBAAiB,CAyFzE;AAOD,sFAAsF;AACtF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,GAAG,IAAI,CAiBzE;
|
|
1
|
+
{"version":3,"file":"feedback.d.ts","sourceRoot":"","sources":["../src/feedback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7C;AAED,MAAM,WAAW,mBAAmB;IAClC,yFAAyF;IACzF,MAAM,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACzD;8DAC0D;IAC1D,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvC,qFAAqF;IACrF,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,8CAA8C;IAC9C,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,0EAA0E;IAC1E,OAAO,IAAI,IAAI,CAAC;IAChB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,KAAK,IAAI,IAAI,CAAC;IACd,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,eAAO,MAAM,kBAAkB,0BAA0B,CAAC;AAC1D,gGAAgG;AAChG,eAAO,MAAM,oBAAoB,SAAU,CAAC;AAC5C,eAAO,MAAM,mBAAmB,OAAO,CAAC;AAKxC,KAAK,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAE7C,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,GAAG,IAAI,CA8BtE;AAaD,+BAA+B;AAC/B,eAAO,MAAM,SAAS,GAAI,GAAG,IAAI,KAAG,MAMnC,CAAC;AAmDF;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,GAAE,QAAmB,GAAG,iBAAiB,CAyFzE;AAOD,sFAAsF;AACtF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,GAAG,IAAI,CAiBzE;AAgDD,eAAO,MAAM,cAAc,KAAK,CAAC;AACjC,eAAO,MAAM,cAAc,IAAI,CAAC;AAChC,kFAAkF;AAClF,eAAO,MAAM,aAAa,IAAI,CAAC;AAC/B,6DAA6D;AAC7D,eAAO,MAAM,mBAAmB,MAAM,CAAC;AACvC,4FAA4F;AAC5F,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAEvC,sFAAsF;AACtF,eAAO,MAAM,KAAK,IAAI,CAAC;AACvB,eAAO,MAAM,IAAI,IAAI,CAAC;AACtB,eAAO,MAAM,GAAG,IAAI,CAAC;AACrB,MAAM,MAAM,SAAS,GAAG,OAAO,KAAK,GAAG,OAAO,IAAI,GAAG,OAAO,GAAG,CAAC;AAEhE,MAAM,WAAW,YAAY;IAC3B,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,MAAM,EAAE,MAAM,CAAC;CAChB;AAKD;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,YAAY,EACjB,cAAc,EAAE,MAAM,EACtB,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,SAAS,GACtC,MAAM,CA4BR;AAkBD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,OAAO,EACb,GAAG,GAAE,QAAmB,GACvB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,SAAS,CAyDrC;AAUD,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,WAAW,GAAG,YAAY,CAAC;AAC7D,eAAO,MAAM,WAAW,KAAK,CAAC;AAC9B,eAAO,MAAM,SAAS,KAAK,CAAC;AAC5B,eAAO,MAAM,UAAU,KAAK,CAAC;AAC7B,eAAO,MAAM,WAAW,KAAK,CAAC;AAC9B,sFAAsF;AACtF,eAAO,MAAM,SAAS,KAAK,CAAC;AAC5B;6CAC6C;AAC7C,eAAO,MAAM,QAAQ,qBAAqB,CAAC;AAE3C,4FAA4F;AAC5F,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,SAAS,CAAC;IACjB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,0FAA0F;AAC1F,eAAO,MAAM,eAAe,GAAI,GAAG,MAAM,EAAE,qBAAoB,KAAG,MAChB,CAAC;AAEnD,8FAA8F;AAC9F,eAAO,MAAM,UAAU,GAAI,WAAW,MAAM,EAAE,WAAW,MAAM,EAAE,iBAAe,KAAG,MACA,CAAC;AAQpF,6DAA6D;AAC7D,wBAAgB,WAAW,CACzB,KAAK,EAAE,SAAS,EAChB,IAAI,EAAE,OAAO,EACb,CAAC,EAAE,MAAM,EACT,IAAI,EAAE,gBAAgB,GACrB,YAAY,CASd;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,YAAY,EAClB,QAAQ,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EAClC,IAAI,EAAE,gBAAgB,GACrB,YAAY,CAUd;AAsFD,qFAAqF;AACrF,eAAO,MAAM,gBAAgB,GAAI,MAAK,QAAmB,KAAG,OACI,CAAC;AAIjE,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,mBAAmB,GAAG,kBAAkB,GAAG,IAAI,CAybzF;AAED,eAAO,MAAM,mBAAmB,QAAO,IAA0B,CAAC"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @goflowstate/widget-sdk v0.3.
|
|
1
|
+
// @goflowstate/widget-sdk v0.3.2
|
|
2
2
|
// https://github.com/goflowstate/goflowstate-sdk
|
|
3
3
|
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -227,21 +227,168 @@ var withTimeout = (work, ms) => new Promise((resolve) => {
|
|
|
227
227
|
}
|
|
228
228
|
);
|
|
229
229
|
});
|
|
230
|
+
var PLACEMENT_BASE = 16;
|
|
231
|
+
var PLACEMENT_STEP = 8;
|
|
232
|
+
var PLACEMENT_PAD = 8;
|
|
233
|
+
var PLACEMENT_MAX_SHARE = 0.6;
|
|
234
|
+
var PLACEMENT_READ_COST = 240;
|
|
235
|
+
var EMPTY = 0;
|
|
236
|
+
var READ = 1;
|
|
237
|
+
var TAP = 2;
|
|
238
|
+
var SAMPLES = 5;
|
|
239
|
+
function choosePlacement(box, viewportHeight, at) {
|
|
240
|
+
const max = Math.max(PLACEMENT_BASE, viewportHeight * PLACEMENT_MAX_SHARE);
|
|
241
|
+
const span = box.size + PLACEMENT_PAD * 2;
|
|
242
|
+
const offsets = Array.from({ length: SAMPLES }, (_, i) => span * i / (SAMPLES - 1));
|
|
243
|
+
const x0 = box.left - PLACEMENT_PAD;
|
|
244
|
+
let best = PLACEMENT_BASE;
|
|
245
|
+
let bestCost = Infinity;
|
|
246
|
+
for (let bottom = PLACEMENT_BASE; bottom <= max; bottom += PLACEMENT_STEP) {
|
|
247
|
+
if (bottom >= bestCost) break;
|
|
248
|
+
const y0 = box.top - (bottom - box.bottom) - PLACEMENT_PAD;
|
|
249
|
+
let worst = EMPTY;
|
|
250
|
+
for (const dy of offsets) {
|
|
251
|
+
for (const dx of offsets) {
|
|
252
|
+
const found = at(x0 + dx, y0 + dy);
|
|
253
|
+
if (found > worst) worst = found;
|
|
254
|
+
if (worst === TAP) break;
|
|
255
|
+
}
|
|
256
|
+
if (worst === TAP) break;
|
|
257
|
+
}
|
|
258
|
+
if (worst === TAP) continue;
|
|
259
|
+
const cost = bottom + (worst === READ ? PLACEMENT_READ_COST : 0);
|
|
260
|
+
if (cost < bestCost) {
|
|
261
|
+
bestCost = cost;
|
|
262
|
+
best = bottom;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return best;
|
|
266
|
+
}
|
|
267
|
+
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]';
|
|
268
|
+
var PICTURE = "img,svg,canvas,video,picture";
|
|
269
|
+
var SURFACE_SHARE = 0.8;
|
|
270
|
+
function occupancyReader(host, doc = document) {
|
|
271
|
+
if (typeof doc.elementsFromPoint !== "function") return () => EMPTY;
|
|
272
|
+
const view = doc.defaultView;
|
|
273
|
+
const width = view?.innerWidth ?? 1;
|
|
274
|
+
const height = view?.innerHeight ?? 1;
|
|
275
|
+
const seen = /* @__PURE__ */ new Map();
|
|
276
|
+
const read = (el) => {
|
|
277
|
+
const known = seen.get(el);
|
|
278
|
+
if (known) return known;
|
|
279
|
+
const style = view ? view.getComputedStyle(el) : null;
|
|
280
|
+
let tap = el.matches(TAPPABLE) || style?.cursor === "pointer";
|
|
281
|
+
if (tap) {
|
|
282
|
+
const r = el.getBoundingClientRect();
|
|
283
|
+
if (r.width * r.height >= width * height * SURFACE_SHARE) tap = false;
|
|
284
|
+
}
|
|
285
|
+
const text = [];
|
|
286
|
+
if (typeof doc.createRange === "function") {
|
|
287
|
+
for (const node of Array.from(el.childNodes)) {
|
|
288
|
+
if (node.nodeType !== 3 || !node.nodeValue || !node.nodeValue.trim()) continue;
|
|
289
|
+
const range = doc.createRange();
|
|
290
|
+
range.selectNodeContents(node);
|
|
291
|
+
if (typeof range.getClientRects === "function")
|
|
292
|
+
text.push(...Array.from(range.getClientRects()));
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const ground = parseColor(style?.backgroundColor);
|
|
296
|
+
const image = style?.backgroundImage ?? "";
|
|
297
|
+
const reading = {
|
|
298
|
+
tap,
|
|
299
|
+
picture: el.matches(PICTURE) || el.closest("svg") !== null,
|
|
300
|
+
text,
|
|
301
|
+
opaque: ground !== null && ground[3] >= 0.5 || image !== "" && image !== "none"
|
|
302
|
+
};
|
|
303
|
+
seen.set(el, reading);
|
|
304
|
+
return reading;
|
|
305
|
+
};
|
|
306
|
+
return (x, y) => {
|
|
307
|
+
const cx = Math.min(Math.max(x, 0), width - 1);
|
|
308
|
+
const cy = Math.min(Math.max(y, 0), height - 1);
|
|
309
|
+
const stack = doc.elementsFromPoint(cx, cy).filter((el) => el !== host && el !== doc.documentElement && el !== doc.body);
|
|
310
|
+
for (let el = stack[0] ?? null; el && el !== doc.body; el = el.parentElement) {
|
|
311
|
+
if (read(el).tap) return TAP;
|
|
312
|
+
}
|
|
313
|
+
for (const el of stack) {
|
|
314
|
+
const r = read(el);
|
|
315
|
+
if (r.picture || r.text.some((t) => cx >= t.left && cx <= t.right && cy >= t.top && cy <= t.bottom))
|
|
316
|
+
return READ;
|
|
317
|
+
if (r.opaque) break;
|
|
318
|
+
}
|
|
319
|
+
return EMPTY;
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
var BUTTON_SIZE = 44;
|
|
323
|
+
var TAB_WIDTH = 26;
|
|
324
|
+
var TAB_HEIGHT = 54;
|
|
325
|
+
var EDGE_MARGIN = 12;
|
|
326
|
+
var DOCK_PAST = 14;
|
|
327
|
+
var SPOT_KEY = "fs-feedback-spot";
|
|
328
|
+
var projectMomentum = (v, deceleration = 0.992) => v / 1e3 * deceleration / (1 - deceleration);
|
|
329
|
+
var rubberband = (overshoot, dimension, constant = 0.55) => overshoot * dimension * constant / (dimension + constant * Math.abs(overshoot));
|
|
330
|
+
var clampY = (y, height, view) => Math.min(
|
|
331
|
+
Math.max(y, view.top + EDGE_MARGIN),
|
|
332
|
+
Math.max(view.top + EDGE_MARGIN, view.height - view.bottom - EDGE_MARGIN - height)
|
|
333
|
+
);
|
|
334
|
+
function restingSpot(state, left, y, view) {
|
|
335
|
+
if (state === "float") {
|
|
336
|
+
return {
|
|
337
|
+
state,
|
|
338
|
+
x: left ? EDGE_MARGIN : view.width - BUTTON_SIZE - EDGE_MARGIN,
|
|
339
|
+
y: clampY(y, BUTTON_SIZE, view)
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
return { state, x: left ? 0 : view.width - TAB_WIDTH, y: clampY(y, TAB_HEIGHT, view) };
|
|
343
|
+
}
|
|
344
|
+
function settleRelease(from, velocity, view) {
|
|
345
|
+
const py = from.y + projectMomentum(velocity.y);
|
|
346
|
+
if (from.state !== "float") return restingSpot(from.state, from.state === "dock-left", py, view);
|
|
347
|
+
const px = from.x + projectMomentum(velocity.x);
|
|
348
|
+
const maxX = view.width - BUTTON_SIZE;
|
|
349
|
+
const tabY = py + (BUTTON_SIZE - TAB_HEIGHT) / 2;
|
|
350
|
+
if (px < -DOCK_PAST || from.x < 3) return restingSpot("dock-left", true, tabY, view);
|
|
351
|
+
if (px > maxX + DOCK_PAST || from.x > maxX - 3)
|
|
352
|
+
return restingSpot("dock-right", false, tabY, view);
|
|
353
|
+
return restingSpot("float", px + BUTTON_SIZE / 2 < view.width / 2, py, view);
|
|
354
|
+
}
|
|
230
355
|
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>';
|
|
231
356
|
var STYLE = `
|
|
232
357
|
:host { all: initial; }
|
|
233
358
|
* { box-sizing: border-box; }
|
|
234
359
|
.bug {
|
|
235
|
-
position: fixed; z-index: 2147483000;
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
width: 44px; height: 44px; border-radius: 50%; padding: 0;
|
|
239
|
-
display: grid; place-items: center; cursor: pointer;
|
|
360
|
+
position: fixed; z-index: 2147483000; left: 0; top: 0;
|
|
361
|
+
width: 44px; height: 44px; border-radius: 22px; padding: 0; margin: 0;
|
|
362
|
+
display: var(--fs-feedback-display, grid); place-items: center; cursor: grab;
|
|
240
363
|
background: var(--surface); color: var(--ink); border: 1px solid var(--line);
|
|
241
|
-
box-shadow: 0
|
|
242
|
-
-webkit-tap-highlight-color: transparent;
|
|
364
|
+
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.2), 0 2px 5px rgba(0, 0, 0, 0.12);
|
|
365
|
+
-webkit-tap-highlight-color: transparent; touch-action: none; user-select: none; -webkit-user-select: none;
|
|
366
|
+
will-change: transform;
|
|
367
|
+
}
|
|
368
|
+
@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
|
|
369
|
+
.bug {
|
|
370
|
+
background: color-mix(in srgb, var(--surface) 74%, transparent);
|
|
371
|
+
-webkit-backdrop-filter: blur(14px) saturate(160%); backdrop-filter: blur(14px) saturate(160%);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
.bug svg { width: 20px; height: 20px; pointer-events: none; }
|
|
375
|
+
.bug.down svg { transform: scale(0.9); }
|
|
376
|
+
.bug.dragging { cursor: grabbing; box-shadow: 0 18px 36px rgba(0, 0, 0, 0.26), 0 3px 8px rgba(0, 0, 0, 0.14); }
|
|
377
|
+
.bug[data-state^="dock"] { width: 26px; height: 54px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.14); opacity: 0.92; }
|
|
378
|
+
.bug[data-state="dock-right"] { border-radius: 16px 0 0 16px; border-right-width: 0; }
|
|
379
|
+
.bug[data-state="dock-left"] { border-radius: 0 16px 16px 0; border-left-width: 0; }
|
|
380
|
+
.bug[data-state^="dock"] svg { width: 16px; height: 16px; }
|
|
381
|
+
.probe {
|
|
382
|
+
position: fixed; visibility: hidden; pointer-events: none; width: 0; height: 0;
|
|
383
|
+
padding: env(safe-area-inset-top, 0px) 0 env(safe-area-inset-bottom, 0px) 0;
|
|
384
|
+
}
|
|
385
|
+
.tuck { align-self: center; font-size: 13px; color: var(--ink-dim); }
|
|
386
|
+
@media (prefers-reduced-transparency: reduce) {
|
|
387
|
+
.bug { background: var(--surface); -webkit-backdrop-filter: none; backdrop-filter: none; }
|
|
388
|
+
}
|
|
389
|
+
@media (prefers-contrast: more) {
|
|
390
|
+
.bug { background: var(--surface); border-color: var(--ink); -webkit-backdrop-filter: none; backdrop-filter: none; }
|
|
243
391
|
}
|
|
244
|
-
.bug svg { width: 20px; height: 20px; }
|
|
245
392
|
.bug:focus-visible, .send:focus-visible, .link:focus-visible, textarea:focus-visible {
|
|
246
393
|
outline: 2px solid var(--ink); outline-offset: 2px;
|
|
247
394
|
}
|
|
@@ -273,12 +420,18 @@ textarea::placeholder { color: var(--ink-dim); }
|
|
|
273
420
|
.status { margin: 0; font-size: 13px; color: var(--ink-dim); min-height: 1.4em; }
|
|
274
421
|
[hidden] { display: none !important; }
|
|
275
422
|
@media (prefers-reduced-motion: no-preference) {
|
|
423
|
+
.bug {
|
|
424
|
+
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),
|
|
425
|
+
border-radius 0.22s cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 0.22s ease;
|
|
426
|
+
}
|
|
427
|
+
.bug svg { transition: transform 0.1s ease-out, width 0.22s ease, height 0.22s ease; }
|
|
276
428
|
.sheet { animation: rise 0.22s ease-out; }
|
|
277
429
|
@keyframes rise { from { transform: translateY(24px); opacity: 0; } to { transform: none; opacity: 1; } }
|
|
278
430
|
}
|
|
279
431
|
`;
|
|
280
432
|
var mounted = null;
|
|
281
433
|
var feedbackOptedOut = (doc = document) => doc.documentElement.getAttribute("data-fs-feedback") === "off";
|
|
434
|
+
var SCROLL_OPTIONS = { capture: true, passive: true };
|
|
282
435
|
function mountFlowFeedback(options) {
|
|
283
436
|
if (typeof document === "undefined" || !document.body) return null;
|
|
284
437
|
if (mounted) return mounted;
|
|
@@ -287,7 +440,8 @@ function mountFlowFeedback(options) {
|
|
|
287
440
|
host.setAttribute(FEEDBACK_HOST_ATTR, "");
|
|
288
441
|
const root = host.attachShadow({ mode: "open" });
|
|
289
442
|
root.innerHTML = `<style>${STYLE}</style>
|
|
290
|
-
<button type="button" class="bug" aria-label="Send feedback on this page">${BUG_ICON}</button>
|
|
443
|
+
<button type="button" class="bug" data-state="float" aria-label="Send feedback on this page">${BUG_ICON}</button>
|
|
444
|
+
<div class="probe" aria-hidden="true"></div>
|
|
291
445
|
<div class="scrim" hidden></div>
|
|
292
446
|
<div class="sheet" role="dialog" aria-modal="true" aria-labelledby="fsf-title" hidden>
|
|
293
447
|
<div class="grab" aria-hidden="true"></div>
|
|
@@ -296,6 +450,7 @@ function mountFlowFeedback(options) {
|
|
|
296
450
|
<textarea aria-labelledby="fsf-title" placeholder="What should change?" maxlength="2000"></textarea>
|
|
297
451
|
<p class="status" role="status" aria-live="polite"></p>
|
|
298
452
|
<button type="button" class="send" disabled>Send feedback</button>
|
|
453
|
+
<button type="button" class="link tuck">Tuck the button to the side</button>
|
|
299
454
|
</div>`;
|
|
300
455
|
const q = (selector) => root.querySelector(selector);
|
|
301
456
|
const bug = q(".bug");
|
|
@@ -307,10 +462,199 @@ function mountFlowFeedback(options) {
|
|
|
307
462
|
const text = q("textarea");
|
|
308
463
|
const status = q(".status");
|
|
309
464
|
const send = q(".send");
|
|
465
|
+
const tuck = q(".tuck");
|
|
466
|
+
const probe = q(".probe");
|
|
310
467
|
let theme = readFlowTheme();
|
|
311
468
|
let screenshot = null;
|
|
312
469
|
let busy = false;
|
|
313
470
|
let closeTimer = null;
|
|
471
|
+
let placeTimer = null;
|
|
472
|
+
let autoBottom = PLACEMENT_BASE;
|
|
473
|
+
let spot = { state: "float", x: 0, y: 0 };
|
|
474
|
+
let placedByPerson = false;
|
|
475
|
+
let velocity = { x: 0, y: 0 };
|
|
476
|
+
let frame = 0;
|
|
477
|
+
const px = (name) => {
|
|
478
|
+
const raw = getComputedStyle(host).getPropertyValue(name).trim();
|
|
479
|
+
const n = raw ? parseFloat(raw) : NaN;
|
|
480
|
+
return Number.isFinite(n) ? n : null;
|
|
481
|
+
};
|
|
482
|
+
const viewport = () => {
|
|
483
|
+
const style = getComputedStyle(probe);
|
|
484
|
+
return {
|
|
485
|
+
width: window.innerWidth,
|
|
486
|
+
height: window.innerHeight,
|
|
487
|
+
top: parseFloat(style.paddingTop) || 0,
|
|
488
|
+
bottom: parseFloat(style.paddingBottom) || 0
|
|
489
|
+
};
|
|
490
|
+
};
|
|
491
|
+
const draw = () => {
|
|
492
|
+
bug.style.transform = `translate3d(${spot.x}px, ${spot.y}px, 0)`;
|
|
493
|
+
};
|
|
494
|
+
const setState = (next) => {
|
|
495
|
+
spot.state = next;
|
|
496
|
+
bug.dataset.state = next;
|
|
497
|
+
};
|
|
498
|
+
const remember = (spot2) => {
|
|
499
|
+
if (!placedByPerson) return;
|
|
500
|
+
try {
|
|
501
|
+
const view = viewport();
|
|
502
|
+
const width = spot2.state === "float" ? BUTTON_SIZE : TAB_WIDTH;
|
|
503
|
+
localStorage.setItem(
|
|
504
|
+
SPOT_KEY,
|
|
505
|
+
JSON.stringify({
|
|
506
|
+
state: spot2.state,
|
|
507
|
+
left: spot2.x + width / 2 < view.width / 2,
|
|
508
|
+
y: view.height > 0 ? spot2.y / view.height : 0
|
|
509
|
+
})
|
|
510
|
+
);
|
|
511
|
+
} catch {
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
const still = () => typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
515
|
+
const springTo = (to, flicked) => {
|
|
516
|
+
cancelAnimationFrame(frame);
|
|
517
|
+
frame = 0;
|
|
518
|
+
setState(to.state);
|
|
519
|
+
remember(to);
|
|
520
|
+
if (still() || typeof requestAnimationFrame !== "function") {
|
|
521
|
+
spot = { ...to };
|
|
522
|
+
velocity = { x: 0, y: 0 };
|
|
523
|
+
draw();
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
const omega = 2 * Math.PI / 0.4;
|
|
527
|
+
const k = omega * omega;
|
|
528
|
+
const c = 2 * (flicked ? 0.8 : 1) * omega;
|
|
529
|
+
let last = performance.now();
|
|
530
|
+
const tick = (now) => {
|
|
531
|
+
const dt = Math.min(0.032, (now - last) / 1e3);
|
|
532
|
+
last = now;
|
|
533
|
+
velocity.x += (-k * (spot.x - to.x) - c * velocity.x) * dt;
|
|
534
|
+
velocity.y += (-k * (spot.y - to.y) - c * velocity.y) * dt;
|
|
535
|
+
spot.x += velocity.x * dt;
|
|
536
|
+
spot.y += velocity.y * dt;
|
|
537
|
+
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;
|
|
538
|
+
if (close2) {
|
|
539
|
+
spot = { ...to };
|
|
540
|
+
velocity = { x: 0, y: 0 };
|
|
541
|
+
frame = 0;
|
|
542
|
+
draw();
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
draw();
|
|
546
|
+
frame = requestAnimationFrame(tick);
|
|
547
|
+
};
|
|
548
|
+
frame = requestAnimationFrame(tick);
|
|
549
|
+
};
|
|
550
|
+
const place = () => {
|
|
551
|
+
placeTimer = null;
|
|
552
|
+
if (!sheet.hidden || bug.hidden || drag || frame) return;
|
|
553
|
+
const view = viewport();
|
|
554
|
+
if (placedByPerson || spot.state !== "float") {
|
|
555
|
+
const width = spot.state === "float" ? BUTTON_SIZE : TAB_WIDTH;
|
|
556
|
+
spot = restingSpot(spot.state, spot.x + width / 2 < view.width / 2, spot.y, view);
|
|
557
|
+
draw();
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
const right = px("--fs-feedback-right") ?? 14;
|
|
561
|
+
const left = view.width - right - BUTTON_SIZE;
|
|
562
|
+
const pinned = px("--fs-feedback-bottom");
|
|
563
|
+
if (pinned === null && typeof document.elementsFromPoint === "function") {
|
|
564
|
+
autoBottom = choosePlacement(
|
|
565
|
+
{
|
|
566
|
+
left,
|
|
567
|
+
top: view.height - view.bottom - autoBottom - BUTTON_SIZE,
|
|
568
|
+
size: BUTTON_SIZE,
|
|
569
|
+
bottom: autoBottom
|
|
570
|
+
},
|
|
571
|
+
view.height,
|
|
572
|
+
occupancyReader(host)
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
const bottom = pinned ?? autoBottom;
|
|
576
|
+
spot = { state: "float", x: left, y: view.height - view.bottom - bottom - BUTTON_SIZE };
|
|
577
|
+
draw();
|
|
578
|
+
};
|
|
579
|
+
const schedulePlace = () => {
|
|
580
|
+
if (placeTimer) clearTimeout(placeTimer);
|
|
581
|
+
placeTimer = setTimeout(place, 150);
|
|
582
|
+
};
|
|
583
|
+
let drag = null;
|
|
584
|
+
let suppressClick = false;
|
|
585
|
+
const onDown = (event) => {
|
|
586
|
+
if (!sheet.hidden || busy) return;
|
|
587
|
+
bug.setPointerCapture?.(event.pointerId);
|
|
588
|
+
cancelAnimationFrame(frame);
|
|
589
|
+
frame = 0;
|
|
590
|
+
velocity = { x: 0, y: 0 };
|
|
591
|
+
drag = {
|
|
592
|
+
id: event.pointerId,
|
|
593
|
+
ox: event.clientX - spot.x,
|
|
594
|
+
oy: event.clientY - spot.y,
|
|
595
|
+
sx: event.clientX,
|
|
596
|
+
sy: event.clientY,
|
|
597
|
+
moved: false,
|
|
598
|
+
history: [{ t: event.timeStamp, x: event.clientX, y: event.clientY }]
|
|
599
|
+
};
|
|
600
|
+
bug.classList.add("down");
|
|
601
|
+
};
|
|
602
|
+
const onMove = (event) => {
|
|
603
|
+
if (!drag || event.pointerId !== drag.id) return;
|
|
604
|
+
const dx = event.clientX - drag.sx;
|
|
605
|
+
const dy = event.clientY - drag.sy;
|
|
606
|
+
if (!drag.moved && Math.hypot(dx, dy) < 8) return;
|
|
607
|
+
if (!drag.moved) {
|
|
608
|
+
drag.moved = true;
|
|
609
|
+
bug.classList.add("dragging");
|
|
610
|
+
}
|
|
611
|
+
drag.history.push({ t: event.timeStamp, x: event.clientX, y: event.clientY });
|
|
612
|
+
if (drag.history.length > 6) drag.history.shift();
|
|
613
|
+
const view = viewport();
|
|
614
|
+
if (spot.state !== "float") {
|
|
615
|
+
const inward = spot.state === "dock-left" ? dx : -dx;
|
|
616
|
+
if (inward <= 18) {
|
|
617
|
+
spot = restingSpot(spot.state, spot.state === "dock-left", event.clientY - drag.oy, view);
|
|
618
|
+
draw();
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
setState("float");
|
|
622
|
+
drag.ox = BUTTON_SIZE / 2;
|
|
623
|
+
drag.oy = BUTTON_SIZE / 2;
|
|
624
|
+
}
|
|
625
|
+
let x = event.clientX - drag.ox;
|
|
626
|
+
let y = event.clientY - drag.oy;
|
|
627
|
+
const maxX = view.width - BUTTON_SIZE;
|
|
628
|
+
const maxY = view.height - BUTTON_SIZE;
|
|
629
|
+
if (x < 0) x = rubberband(x, 60);
|
|
630
|
+
else if (x > maxX) x = maxX + rubberband(x - maxX, 60);
|
|
631
|
+
if (y < 0) y = rubberband(y, 80);
|
|
632
|
+
else if (y > maxY) y = maxY + rubberband(y - maxY, 80);
|
|
633
|
+
spot.x = x;
|
|
634
|
+
spot.y = y;
|
|
635
|
+
draw();
|
|
636
|
+
};
|
|
637
|
+
const onUp = (event) => {
|
|
638
|
+
if (!drag || event.pointerId !== drag.id) return;
|
|
639
|
+
const ended = drag;
|
|
640
|
+
drag = null;
|
|
641
|
+
bug.classList.remove("down", "dragging");
|
|
642
|
+
if (!ended.moved) return;
|
|
643
|
+
suppressClick = true;
|
|
644
|
+
setTimeout(() => {
|
|
645
|
+
suppressClick = false;
|
|
646
|
+
}, 0);
|
|
647
|
+
const first = ended.history[0];
|
|
648
|
+
const last = ended.history[ended.history.length - 1];
|
|
649
|
+
const dt = Math.max(1, last.t - first.t);
|
|
650
|
+
const paused = event.timeStamp - last.t > 90;
|
|
651
|
+
velocity = paused ? { x: 0, y: 0 } : { x: (last.x - first.x) / dt * 1e3, y: (last.y - first.y) / dt * 1e3 };
|
|
652
|
+
placedByPerson = true;
|
|
653
|
+
springTo(settleRelease(spot, velocity, viewport()), Math.hypot(velocity.x, velocity.y) > 350);
|
|
654
|
+
};
|
|
655
|
+
const observer = typeof MutationObserver === "function" ? new MutationObserver((records) => {
|
|
656
|
+
if (records.some((r) => r.target !== host)) schedulePlace();
|
|
657
|
+
}) : null;
|
|
314
658
|
const paint = () => {
|
|
315
659
|
theme = readFlowTheme();
|
|
316
660
|
const vars = {
|
|
@@ -350,6 +694,7 @@ function mountFlowFeedback(options) {
|
|
|
350
694
|
status.textContent = "";
|
|
351
695
|
busy = false;
|
|
352
696
|
sync();
|
|
697
|
+
schedulePlace();
|
|
353
698
|
};
|
|
354
699
|
const open = async () => {
|
|
355
700
|
if (!sheet.hidden || busy) return;
|
|
@@ -360,6 +705,7 @@ function mountFlowFeedback(options) {
|
|
|
360
705
|
const url = await withTimeout(Promise.resolve().then(capture), CAPTURE_TIMEOUT_MS);
|
|
361
706
|
busy = false;
|
|
362
707
|
setShot(url);
|
|
708
|
+
tuck.hidden = spot.state !== "float";
|
|
363
709
|
scrim.hidden = false;
|
|
364
710
|
sheet.hidden = false;
|
|
365
711
|
sync();
|
|
@@ -390,7 +736,28 @@ function mountFlowFeedback(options) {
|
|
|
390
736
|
sync();
|
|
391
737
|
}
|
|
392
738
|
};
|
|
393
|
-
bug.addEventListener("click", () =>
|
|
739
|
+
bug.addEventListener("click", () => {
|
|
740
|
+
if (!suppressClick) void open();
|
|
741
|
+
});
|
|
742
|
+
bug.addEventListener("pointerdown", onDown);
|
|
743
|
+
bug.addEventListener("pointermove", onMove);
|
|
744
|
+
bug.addEventListener("pointerup", onUp);
|
|
745
|
+
bug.addEventListener("pointercancel", onUp);
|
|
746
|
+
tuck.addEventListener("click", () => {
|
|
747
|
+
const view = viewport();
|
|
748
|
+
const left = spot.x + BUTTON_SIZE / 2 < view.width / 2;
|
|
749
|
+
close();
|
|
750
|
+
placedByPerson = true;
|
|
751
|
+
springTo(
|
|
752
|
+
restingSpot(
|
|
753
|
+
left ? "dock-left" : "dock-right",
|
|
754
|
+
left,
|
|
755
|
+
spot.y + (BUTTON_SIZE - TAB_HEIGHT) / 2,
|
|
756
|
+
view
|
|
757
|
+
),
|
|
758
|
+
false
|
|
759
|
+
);
|
|
760
|
+
});
|
|
394
761
|
scrim.addEventListener("click", close);
|
|
395
762
|
remove.addEventListener("click", () => setShot(null));
|
|
396
763
|
text.addEventListener("input", sync);
|
|
@@ -404,6 +771,29 @@ function mountFlowFeedback(options) {
|
|
|
404
771
|
});
|
|
405
772
|
document.body.appendChild(host);
|
|
406
773
|
paint();
|
|
774
|
+
try {
|
|
775
|
+
const saved = JSON.parse(localStorage.getItem(SPOT_KEY) ?? "null");
|
|
776
|
+
if (saved && typeof saved.y === "number" && (saved.state === "float" || saved.state === "dock-left" || saved.state === "dock-right")) {
|
|
777
|
+
const view = viewport();
|
|
778
|
+
placedByPerson = true;
|
|
779
|
+
setState(saved.state);
|
|
780
|
+
spot = restingSpot(saved.state, saved.left === true, saved.y * view.height, view);
|
|
781
|
+
}
|
|
782
|
+
} catch {
|
|
783
|
+
}
|
|
784
|
+
place();
|
|
785
|
+
schedulePlace();
|
|
786
|
+
observer?.observe(document.body, {
|
|
787
|
+
childList: true,
|
|
788
|
+
subtree: true,
|
|
789
|
+
attributes: true,
|
|
790
|
+
attributeFilter: ["class", "style", "hidden"]
|
|
791
|
+
});
|
|
792
|
+
window.addEventListener("resize", schedulePlace);
|
|
793
|
+
window.addEventListener("load", schedulePlace);
|
|
794
|
+
window.addEventListener("scroll", schedulePlace, SCROLL_OPTIONS);
|
|
795
|
+
document.addEventListener("transitionend", schedulePlace, SCROLL_OPTIONS);
|
|
796
|
+
document.addEventListener("animationend", schedulePlace, SCROLL_OPTIONS);
|
|
407
797
|
options.onReady?.();
|
|
408
798
|
mounted = {
|
|
409
799
|
host,
|
|
@@ -414,6 +804,14 @@ function mountFlowFeedback(options) {
|
|
|
414
804
|
close,
|
|
415
805
|
unmount: () => {
|
|
416
806
|
if (closeTimer) clearTimeout(closeTimer);
|
|
807
|
+
if (placeTimer) clearTimeout(placeTimer);
|
|
808
|
+
cancelAnimationFrame(frame);
|
|
809
|
+
observer?.disconnect();
|
|
810
|
+
window.removeEventListener("resize", schedulePlace);
|
|
811
|
+
window.removeEventListener("load", schedulePlace);
|
|
812
|
+
window.removeEventListener("scroll", schedulePlace, SCROLL_OPTIONS);
|
|
813
|
+
document.removeEventListener("transitionend", schedulePlace, SCROLL_OPTIONS);
|
|
814
|
+
document.removeEventListener("animationend", schedulePlace, SCROLL_OPTIONS);
|
|
417
815
|
host.remove();
|
|
418
816
|
mounted = null;
|
|
419
817
|
}
|