@floegence/floe-webapp-core 0.70.0 → 0.71.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,22 @@
1
+ /* A real input anchors the operating system's IME without covering the surface. */
2
+ .floe-remote-input {
3
+ position: fixed;
4
+ z-index: 10;
5
+ width: 1px;
6
+ height: 1px;
7
+ padding: 0;
8
+ margin: 0;
9
+ border: 0;
10
+ opacity: 0;
11
+ pointer-events: none;
12
+ resize: none;
13
+ overflow: hidden;
14
+ font: 16px/1.5 sans-serif;
15
+ }
16
+ .floe-remote-input[data-composing] {
17
+ width: 20rem;
18
+ height: 1.5em;
19
+ opacity: 1;
20
+ color: var(--foreground, CanvasText);
21
+ background: var(--background, Canvas);
22
+ }
@@ -0,0 +1,38 @@
1
+ /** Client-owned editing for a remote surface. No document contents are mirrored. */
2
+ export interface RemoteInputKey {
3
+ key: string;
4
+ code: string;
5
+ pressed: boolean;
6
+ repeat: boolean;
7
+ shiftKey: boolean;
8
+ ctrlKey: boolean;
9
+ altKey: boolean;
10
+ metaKey: boolean;
11
+ location: number;
12
+ }
13
+ export interface RemoteInputOptions<T> {
14
+ surface: HTMLElement;
15
+ label: string;
16
+ commitText(text: string, target: T): void;
17
+ sendKey(key: RemoteInputKey, target: T): void;
18
+ release(target: T): void;
19
+ /** Existing clipboard transport may own a shortcut; never submit it twice. */
20
+ clipboard?: (event: KeyboardEvent, target: T) => boolean;
21
+ onKeyboardVisibilityChange?: (visible: boolean) => void;
22
+ }
23
+ export interface RemoteInputController<T> {
24
+ readonly element: HTMLTextAreaElement;
25
+ bindTarget(target: T | null): void;
26
+ setAnchor(clientX: number, clientY: number): void;
27
+ focus(): void;
28
+ setKeyboardVisible(visible: boolean): void;
29
+ release(): void;
30
+ reset(): void;
31
+ dispose(): void;
32
+ }
33
+ /**
34
+ * The caller gives this controller exclusive keyboard/editing ownership of its
35
+ * surface. Pointer transport and clipboard synchronization remain caller-owned.
36
+ * Targets are immutable identity tokens; bind a new token when ownership changes.
37
+ */
38
+ export declare function createRemoteInput<T>(options: RemoteInputOptions<T>): RemoteInputController<T>;
@@ -0,0 +1,145 @@
1
+ function I(a) {
2
+ const { surface: M } = a, k = M.ownerDocument, c = k.defaultView;
3
+ if (!c) throw new Error("Remote input requires an attached document");
4
+ const t = k.createElement("textarea");
5
+ t.className = "floe-remote-input", t.setAttribute("aria-label", a.label), t.setAttribute("autocorrect", "off"), t.autocomplete = "off", t.autocapitalize = "off", t.spellcheck = !1, t.wrap = "off", t.disabled = !0, t.tabIndex = -1, k.body.append(t);
6
+ let l = null, y = 0, u = !1, V = !1, o = null, p = !1, s = null;
7
+ const h = /* @__PURE__ */ new Set(), w = /* @__PURE__ */ new Set();
8
+ let C = null;
9
+ const E = [];
10
+ function n(e, i, r) {
11
+ const g = r;
12
+ e.addEventListener(i, g), E.push(() => e.removeEventListener(i, g));
13
+ }
14
+ const b = (e) => e.code || e.key, x = (e, i) => ({
15
+ key: e.key,
16
+ code: e.code,
17
+ pressed: i,
18
+ repeat: e.repeat,
19
+ location: e.location,
20
+ shiftKey: e.shiftKey,
21
+ ctrlKey: e.ctrlKey,
22
+ altKey: e.altKey,
23
+ metaKey: e.metaKey
24
+ });
25
+ function K(e) {
26
+ V !== e && (V = e, a.onKeyboardVisibilityChange?.(e));
27
+ }
28
+ function f() {
29
+ const e = M.getBoundingClientRect(), i = c.visualViewport, r = Math.max(e.left, i?.offsetLeft ?? 0), g = Math.max(e.top, i?.offsetTop ?? 0), D = Math.min(e.right, (i?.offsetLeft ?? 0) + (i?.width ?? c.innerWidth)), F = Math.min(e.bottom, (i?.offsetTop ?? 0) + (i?.height ?? c.innerHeight)), L = Math.max(1, Math.min(320, D - r - 8));
30
+ t.style.maxWidth = `${L}px`;
31
+ const S = t.getBoundingClientRect(), P = C?.x ?? r + 12, R = C?.y ?? g + 12;
32
+ t.style.left = `${Math.max(r, Math.min(P, D - S.width - 4))}px`, t.style.top = `${Math.max(g, Math.min(R, F - S.height - 4))}px`;
33
+ }
34
+ function d() {
35
+ t.value = "", delete t.dataset.composing, s = null;
36
+ }
37
+ function B() {
38
+ const e = l, i = h.size > 0;
39
+ h.clear(), e !== null && i && a.release(e);
40
+ }
41
+ function m() {
42
+ y++, o && (o.cancelled = !0), p = !0, d(), B(), o && k.activeElement === t && t.blur();
43
+ }
44
+ function T(e) {
45
+ !e || l === null || u || a.commitText(e, l);
46
+ }
47
+ function z(e) {
48
+ if (l === null) return;
49
+ const i = { key: e, code: e, pressed: !0, repeat: !1, shiftKey: !1, ctrlKey: !1, altKey: !1, metaKey: !1, location: 0 }, r = l;
50
+ a.sendKey(i, r), l === r && a.sendKey({ ...i, pressed: !1 }, r);
51
+ }
52
+ function A(e) {
53
+ if (l === null || u) {
54
+ d();
55
+ return;
56
+ }
57
+ if (o?.cancelled || o && o.epoch !== y) {
58
+ d();
59
+ return;
60
+ }
61
+ if (e.isComposing) {
62
+ o ??= { epoch: y, committed: !1, cancelled: !1 }, t.dataset.composing = "true", f();
63
+ return;
64
+ }
65
+ if (o) {
66
+ !o.committed && t.value && (o.committed = !0, T(t.value)), d();
67
+ return;
68
+ }
69
+ if (p || e.inputType === "insertFromComposition") {
70
+ p = !1, d();
71
+ return;
72
+ }
73
+ const i = t.value;
74
+ if (s && i === s.key && !s.altKey && i.length === 1) {
75
+ const r = s;
76
+ h.add(r.code || r.key), a.sendKey(r, l);
77
+ } else T(i);
78
+ d();
79
+ }
80
+ return n(t, "keydown", (e) => {
81
+ if (!(l === null || u)) {
82
+ if (e.stopPropagation(), o?.cancelled && !e.isComposing && e.key !== "Process" && e.keyCode !== 229 && (o = null), e.isComposing || o || e.key === "Process" || e.keyCode === 229) {
83
+ w.add(b(e)), s = null;
84
+ return;
85
+ }
86
+ if (w.delete(b(e)), p = !1, a.clipboard?.(e, l)) {
87
+ s = null;
88
+ return;
89
+ }
90
+ if (e.key === "Dead" || e.key === "Unidentified") {
91
+ s = null;
92
+ return;
93
+ }
94
+ if (Array.from(e.key).length === 1 && !e.metaKey && (!e.ctrlKey || e.getModifierState("AltGraph"))) {
95
+ s = x(e, !0);
96
+ return;
97
+ }
98
+ e.preventDefault(), h.add(b(e)), a.sendKey(x(e, !0), l);
99
+ }
100
+ }), n(t, "keyup", (e) => {
101
+ e.stopPropagation(), s = null, !w.delete(b(e)) && l !== null && h.delete(b(e)) && a.sendKey(x(e, !1), l);
102
+ }), n(t, "beforeinput", (e) => {
103
+ if (l === null || u) {
104
+ e.preventDefault();
105
+ return;
106
+ }
107
+ if (e.isComposing || o || e.inputType === "insertCompositionText" || e.inputType === "deleteCompositionText" || e.inputType === "insertFromComposition") return;
108
+ p = !1;
109
+ const r = { deleteContentBackward: "Backspace", deleteContentForward: "Delete", insertLineBreak: "Enter", insertParagraph: "Enter" }[e.inputType];
110
+ r && (e.preventDefault(), d(), z(r));
111
+ }), n(t, "input", A), n(t, "compositionstart", () => {
112
+ o = { epoch: y, committed: !1, cancelled: l === null }, p = !1, s = null, t.dataset.composing = "true", f();
113
+ }), n(t, "compositionend", (e) => {
114
+ const i = o;
115
+ o = null, p = !0, d(), i && !i.cancelled && !i.committed && i.epoch === y && T(e.data);
116
+ }), n(t, "blur", () => {
117
+ m(), K(!1);
118
+ }), n(c, "blur", m), n(c, "pagehide", m), n(c, "resize", f), c.visualViewport && (n(c.visualViewport, "resize", f), n(c.visualViewport, "scroll", f)), f(), {
119
+ element: t,
120
+ bindTarget(e) {
121
+ u || Object.is(l, e) || (m(), l = e, t.disabled = e === null);
122
+ },
123
+ setAnchor(e, i) {
124
+ !Number.isFinite(e) || !Number.isFinite(i) || (C = { x: e, y: i }, f());
125
+ },
126
+ focus() {
127
+ l === null || u || (t.focus({ preventScroll: !0 }), f());
128
+ },
129
+ setKeyboardVisible(e) {
130
+ e && l !== null && !u ? (t.focus({ preventScroll: !0 }), K(!0), f()) : (t.blur(), K(!1));
131
+ },
132
+ release: B,
133
+ reset: m,
134
+ dispose() {
135
+ if (!u) {
136
+ m(), u = !0, l = null;
137
+ for (const e of E) e();
138
+ t.remove(), K(!1);
139
+ }
140
+ }
141
+ };
142
+ }
143
+ export {
144
+ I as createRemoteInput
145
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floegence/floe-webapp-core",
3
- "version": "0.70.0",
3
+ "version": "0.71.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -108,6 +108,7 @@
108
108
  },
109
109
  "./progress-shimmer.css": "./dist/progress-shimmer.css",
110
110
  "./window-status.css": "./dist/window-status.css",
111
+ "./remote-input.css": "./dist/remote-input.css",
111
112
  "./window-status": {
112
113
  "types": "./dist/window-status.d.ts",
113
114
  "import": "./dist/window-status.js",
@@ -116,6 +117,10 @@
116
117
  "./reload-placeholder": {
117
118
  "types": "./dist/reload-placeholder.d.ts",
118
119
  "import": "./dist/reload-placeholder.js"
120
+ },
121
+ "./remote-input": {
122
+ "types": "./dist/remote-input.d.ts",
123
+ "import": "./dist/remote-input.js"
119
124
  }
120
125
  },
121
126
  "files": [