@floegence/floe-webapp-core 0.76.0 → 0.77.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.
@@ -0,0 +1,16 @@
1
+ /* Apply only to remote pixels, never local chrome or the editable IME element. */
2
+ [data-floe-remote-pointer] {
3
+ touch-action: pinch-zoom;
4
+ user-select: none;
5
+ -webkit-user-select: none;
6
+ -webkit-touch-callout: none;
7
+ }
8
+ .floe-remote-pointer-hold {
9
+ position: fixed;
10
+ width: 28px;
11
+ height: 28px;
12
+ margin: -14px;
13
+ border: 2px solid var(--ring, #808080);
14
+ border-radius: 50%;
15
+ pointer-events: none;
16
+ }
@@ -0,0 +1,47 @@
1
+ /** Viewport CSS coordinates and browser modifiers, independent of image density. */
2
+ export interface RemotePointerPosition {
3
+ clientX: number;
4
+ clientY: number;
5
+ shiftKey: boolean;
6
+ ctrlKey: boolean;
7
+ altKey: boolean;
8
+ metaKey: boolean;
9
+ pointerType: string;
10
+ }
11
+ export type RemotePointerCommand = RemotePointerPosition & ({
12
+ kind: 'move';
13
+ } | {
14
+ kind: 'down' | 'up';
15
+ button: number;
16
+ clicks: number;
17
+ } | {
18
+ kind: 'scroll';
19
+ dx: number;
20
+ dy: number;
21
+ });
22
+ export interface RemotePointerOptions<T> {
23
+ /** Stable owner; resolveTarget excludes local chrome and unpainted content. */
24
+ surface: HTMLElement;
25
+ resolveTarget(event: MouseEvent): T | null;
26
+ isTargetValid(target: T): boolean;
27
+ sendPointer(command: RemotePointerCommand, target: T): void | boolean;
28
+ /** Clear platform scroll remainder; never release keyboard input here. */
29
+ release(target: T): void;
30
+ /** Called before a deliberate button press, never for touch scrolling. */
31
+ onActivate?(position: RemotePointerPosition, target: T): void;
32
+ onHoldChange?(position: (RemotePointerPosition & {
33
+ target: T;
34
+ }) | null): void;
35
+ }
36
+ export interface RemotePointerController {
37
+ /** Preserve ordering before the consumer delivers a later key/text command. */
38
+ flush(): void;
39
+ reset(): void;
40
+ dispose(): void;
41
+ }
42
+ /**
43
+ * Own remote content pointer events once. Mark its content elements with
44
+ * data-floe-remote-pointer and load remote-pointer.css before interaction.
45
+ * Keyboard, local window decorations and transport remain separate owners.
46
+ */
47
+ export declare function createRemotePointer<T>(options: RemotePointerOptions<T>): RemotePointerController;
@@ -0,0 +1,198 @@
1
+ function U(a) {
2
+ const { surface: l } = a, I = l.ownerDocument, c = I.defaultView;
3
+ if (!c) throw new Error("Remote pointer requires an attached document");
4
+ let o = null, f = null, u = null, p = null, y = 0, z = 0, M = !1, x = !1, D = 0, b = !1;
5
+ const g = /* @__PURE__ */ new Set(), L = /* @__PURE__ */ new Set(), h = /* @__PURE__ */ new Map(), C = [], w = (e) => !x && a.isTargetValid(e), O = (e, t) => Math.hypot(e.clientX - t.clientX, e.clientY - t.clientY), v = (e) => ({
6
+ clientX: e.clientX,
7
+ clientY: e.clientY,
8
+ shiftKey: e.shiftKey,
9
+ ctrlKey: e.ctrlKey,
10
+ altKey: e.altKey,
11
+ metaKey: e.metaKey,
12
+ pointerType: "pointerType" in e ? String(e.pointerType) : "mouse"
13
+ });
14
+ function d(e, t, n, i = !1) {
15
+ const s = n;
16
+ e.addEventListener(t, s, { capture: i, passive: !1 }), C.push(() => e.removeEventListener(t, s, i));
17
+ }
18
+ function P(e, t) {
19
+ const n = w(t) && a.sendPointer(e, t) !== !1;
20
+ if (n && e.kind === "move")
21
+ for (const i of h.values()) i.target === t && (i.command = { ...i.command, ...v(e) });
22
+ return n;
23
+ }
24
+ function H() {
25
+ c.clearTimeout(z), z = 0, M && (M = !1, a.onHoldChange?.(null));
26
+ }
27
+ function X(e) {
28
+ L.delete(e), l.hasPointerCapture?.(e) && l.releasePointerCapture(e);
29
+ }
30
+ function B(e) {
31
+ l.setPointerCapture?.(e), L.add(e);
32
+ }
33
+ function k() {
34
+ y && c.cancelAnimationFrame(y), y = 0;
35
+ const e = f;
36
+ f = null, e && !P(e.command, e.target) && r();
37
+ }
38
+ function E(e, t) {
39
+ if (f && (f.target !== t || f.command.kind !== e.kind) && k(), !!w(t) && (e.kind === "scroll" && f?.command.kind === "scroll" && (e = { ...e, dx: f.command.dx + e.dx, dy: f.command.dy + e.dy }), f = { target: t, command: e }, !y)) {
40
+ const n = D, i = c.requestAnimationFrame(() => {
41
+ n === D && y === i && k();
42
+ });
43
+ y = i;
44
+ }
45
+ }
46
+ function T(e, t, n, i, s = 1) {
47
+ const m = h.get(i);
48
+ if (!e && !m) return;
49
+ e || h.delete(i);
50
+ const A = { ...t, kind: e ? "down" : "up", button: i, clicks: s };
51
+ P(A, n) && e && h.set(i, { target: n, command: A });
52
+ }
53
+ function K(e, t) {
54
+ const n = D;
55
+ return a.onActivate?.(e, t), n === D && w(t);
56
+ }
57
+ function Y(e, t, n, i = 1) {
58
+ K(e, t) && (T(!0, e, t, n, i), T(!1, e, t, n, i));
59
+ }
60
+ function r() {
61
+ D++, H(), y && c.cancelAnimationFrame(y), y = 0;
62
+ const e = /* @__PURE__ */ new Set();
63
+ o && e.add(o.target), f && e.add(f.target), u !== null && e.add(u), f = null, o = null, u = null, p = null, b = g.size > 0;
64
+ for (const [t, n] of h)
65
+ e.add(n.target), T(!1, n.command, n.target, t);
66
+ for (const t of e) a.release(t);
67
+ for (const t of [...L]) X(t);
68
+ }
69
+ function S(e) {
70
+ g.delete(e), X(e), g.size || (b = !1);
71
+ }
72
+ function _(e) {
73
+ const t = o;
74
+ if (!t || t.id !== e.pointerId) return;
75
+ if (!w(t.target)) {
76
+ r();
77
+ return;
78
+ }
79
+ const n = v(e);
80
+ if ((t.mode === "pending" || t.mode === "hold") && O(t.origin, n) > 8) {
81
+ const i = t.mode === "hold";
82
+ if (H(), p = null, t.mode = i ? "drag" : "scroll", i && K(t.origin, t.target) && T(!0, t.origin, t.target, 0), o !== t) return;
83
+ }
84
+ if (t.mode === "scroll") {
85
+ const i = t.last, s = i.clientX - n.clientX, m = i.clientY - n.clientY;
86
+ (s || m) && E({ ...t.origin, kind: "scroll", dx: s, dy: m }, t.target), t.last = n;
87
+ } else t.mode === "drag" && (E({ ...n, kind: "move" }, t.target), t.last = n);
88
+ }
89
+ d(l, "pointerdown", (e) => {
90
+ const t = a.resolveTarget(e);
91
+ if (t === null || !w(t)) return;
92
+ const n = v(e);
93
+ if (e.pointerType === "touch") {
94
+ if (g.add(e.pointerId), g.size > 1 || b) {
95
+ r();
96
+ return;
97
+ }
98
+ (o || h.size) && r(), k(), u !== null && (a.release(u), u = null), b = !1, o = { id: e.pointerId, target: t, origin: n, last: n, mode: "pending" };
99
+ const i = o;
100
+ z = c.setTimeout(() => {
101
+ if (z = 0, o === i) {
102
+ if (!w(t)) {
103
+ r();
104
+ return;
105
+ }
106
+ i.mode = "hold", M = !0, a.onHoldChange?.({ ...n, target: t });
107
+ }
108
+ }, 450);
109
+ } else {
110
+ if (g.size) {
111
+ r();
112
+ return;
113
+ }
114
+ o && (o.id !== e.pointerId || o.target !== t) && r(), k(), u !== null && (a.release(u), u = null), o = { id: e.pointerId, target: t, origin: n, last: n, mode: "direct" };
115
+ const i = e.button === 0 && p?.target === t && c.performance.now() - p.time <= 350 && O(p.position, n) <= 16;
116
+ K(n, t) && T(!0, n, t, e.button, Math.max(1, e.detail, i ? 2 : 1));
117
+ }
118
+ B(e.pointerId), e.pointerType === "touch" && e.preventDefault();
119
+ }), d(l, "pointermove", (e) => {
120
+ if (e.pointerType === "touch") {
121
+ _(e);
122
+ return;
123
+ }
124
+ if (b || g.size) return;
125
+ const t = o?.target ?? a.resolveTarget(e);
126
+ if (t === null || !w(t)) {
127
+ o && r();
128
+ return;
129
+ }
130
+ const n = v(e);
131
+ if (o) {
132
+ o.last = n;
133
+ for (const [i, s] of [[0, 1], [1, 4], [2, 2], [3, 8], [4, 16]]) {
134
+ const m = !!(e.buttons & s);
135
+ m !== h.has(i) && (k(), T(m, n, t, i));
136
+ }
137
+ }
138
+ E({ ...n, kind: "move" }, t);
139
+ }), d(l, "pointerup", (e) => {
140
+ const t = o;
141
+ if (t?.id === e.pointerId && !b)
142
+ if (!w(t.target)) r();
143
+ else if (e.pointerType === "touch") {
144
+ _(e), H();
145
+ const n = v(e);
146
+ if (t.mode === "pending") {
147
+ const i = c.performance.now(), s = p?.target === t.target && i - p.time <= 350 && O(p.position, n) <= 16;
148
+ Y(n, t.target, 0, s ? 2 : 1), p = s ? null : { target: t.target, position: n, time: i };
149
+ } else t.mode === "hold" ? (p = null, Y(n, t.target, 2)) : (k(), t.mode === "drag" && T(!1, n, t.target, 0));
150
+ a.release(t.target), o = null;
151
+ } else {
152
+ f?.command.kind === "move" && (f.command = { ...v(e), kind: "move" }), k();
153
+ const n = h.get(e.button)?.command;
154
+ for (const i of [...h.keys()]) T(!1, v(e), t.target, i, n && "clicks" in n ? n.clicks : 1);
155
+ p = e.button === 0 && n && "clicks" in n && n.clicks === 1 && O(t.origin, v(e)) <= 8 ? { target: t.target, position: v(e), time: c.performance.now() } : null, h.size || (o = null);
156
+ }
157
+ (!o || o.id !== e.pointerId) && S(e.pointerId);
158
+ }), d(l, "pointercancel", (e) => {
159
+ r(), S(e.pointerId);
160
+ }), d(l, "lostpointercapture", (e) => {
161
+ L.has(e.pointerId) && (r(), S(e.pointerId));
162
+ }), d(I, "pointerup", (e) => {
163
+ o?.id !== e.pointerId && S(e.pointerId);
164
+ }), d(I, "pointercancel", (e) => S(e.pointerId)), d(I, "pointerdown", (e) => {
165
+ o && (e.pointerType === "touch" && e.pointerId !== o.id ? (g.add(e.pointerId), r()) : l.contains(e.target) || r());
166
+ }, !0), d(l, "wheel", (e) => {
167
+ const t = a.resolveTarget(e);
168
+ if (t === null || !w(t) || (e.preventDefault(), o || g.size)) return;
169
+ p = null, u !== null && u !== t && (k(), a.release(u)), u = t;
170
+ const n = e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? l.clientWidth || c.innerWidth : 1, i = e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? l.clientHeight || c.innerHeight : 1, s = e.deltaX * n, m = e.deltaY * i;
171
+ Number.isFinite(s) && Number.isFinite(m) && (s || m) && E({ ...v(e), kind: "scroll", dx: s, dy: m }, t);
172
+ });
173
+ for (const e of ["mousedown", "mouseup", "click", "dblclick", "contextmenu", "dragstart", "selectstart"])
174
+ d(l, e, (t) => {
175
+ a.resolveTarget(t) !== null && t.preventDefault();
176
+ });
177
+ for (const e of ["blur", "pagehide", "resize", "orientationchange"]) d(c, e, r);
178
+ if (d(I, "visibilitychange", () => {
179
+ I.hidden && r();
180
+ }), d(c, "scroll", r, !0), c.visualViewport && (d(c.visualViewport, "resize", r), d(c.visualViewport, "scroll", r)), typeof ResizeObserver < "u") {
181
+ let e = `${l.clientWidth}:${l.clientHeight}`;
182
+ const t = new ResizeObserver(() => {
183
+ const n = `${l.clientWidth}:${l.clientHeight}`;
184
+ n !== e && (e = n, r());
185
+ });
186
+ t.observe(l), C.push(() => t.disconnect());
187
+ }
188
+ return { flush: k, reset: r, dispose() {
189
+ if (!x) {
190
+ r(), x = !0;
191
+ for (const e of C) e();
192
+ g.clear();
193
+ }
194
+ } };
195
+ }
196
+ export {
197
+ U as createRemotePointer
198
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floegence/floe-webapp-core",
3
- "version": "0.76.0",
3
+ "version": "0.77.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -125,7 +125,12 @@
125
125
  "./viewport": {
126
126
  "types": "./dist/viewport.d.ts",
127
127
  "import": "./dist/viewport.js"
128
- }
128
+ },
129
+ "./remote-pointer": {
130
+ "types": "./dist/remote-pointer.d.ts",
131
+ "import": "./dist/remote-pointer.js"
132
+ },
133
+ "./remote-pointer.css": "./dist/remote-pointer.css"
129
134
  },
130
135
  "files": [
131
136
  "dist",