@floegence/floe-webapp-core 0.69.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,24 @@
1
+ export interface ReloadPlaceholderOptions {
2
+ /** Session-only presentation storage; never put resource data in this record. */
3
+ storageKey: string;
4
+ /** Optional session-storage identity to isolate navigation between environments. */
5
+ scopeStorageKey?: string;
6
+ }
7
+ export interface ReloadPlaceholder {
8
+ active(): boolean;
9
+ /** Keep the matching content placeholder while the live shell becomes usable. */
10
+ restrictTo(element: Element): void;
11
+ finish(): void;
12
+ /** Capture only anonymous geometry on pagehide, from the current authorized view. */
13
+ arm(root: Element, surfaces: string): void;
14
+ /** Explicit revocation discards the placeholder and its session record. */
15
+ clear(): void;
16
+ }
17
+ declare global {
18
+ interface Window {
19
+ __floeReloadPlaceholder?: ReloadPlaceholder;
20
+ }
21
+ }
22
+ /** Insert before external scripts/styles. It restores anonymous layout, never cached facts. */
23
+ export declare function createReloadPlaceholderScript(options: ReloadPlaceholderOptions): string;
24
+ export declare function getReloadPlaceholder(): ReloadPlaceholder | undefined;
@@ -0,0 +1,124 @@
1
+ function P(h) {
2
+ const s = document, $ = 48e3, b = 240, A = () => h.scopeStorageKey ? sessionStorage.getItem(h.scopeStorageKey) ?? "" : "", g = (o) => typeof o == "string" && o.length <= 120 && !/[;{}<>\\]|url\(|var\(/i.test(o) && CSS.supports("color", o);
3
+ let l, a, C = "", d;
4
+ try {
5
+ const o = sessionStorage.getItem(h.storageKey), e = o && o.length <= $ ? JSON.parse(o) : void 0;
6
+ e?.version === 1 && e.scope === A() && e.width === innerWidth && e.height === innerHeight && g(e.background) && Array.isArray(e.scroll) && e.scroll.length <= 12 && e.scroll.every((r) => Array.isArray(r) && r.length === 3 && /^[a-z0-9-]{1,48}$/.test(r[0]) && r.slice(1).every((i) => typeof i == "number" && Number.isFinite(i) && i >= 0 && i <= 1e7)) && Array.isArray(e.boxes) && e.boxes.length > 0 && e.boxes.length <= b && e.boxes.every((r) => Array.isArray(r) && r.length === 7 && r.slice(0, 5).every((i) => typeof i == "number" && Number.isFinite(i) && i >= 0 && i <= 2e4) && g(r[5]) && g(r[6])) && (d = e);
7
+ } catch {
8
+ }
9
+ const M = () => {
10
+ if (!(!d || !s.body)) {
11
+ l = s.createElement("div"), l.dataset.floeReloadPlaceholder = "", l.setAttribute("aria-hidden", "true"), l.inert = !0, Object.assign(l.style, {
12
+ position: "fixed",
13
+ inset: "0",
14
+ zIndex: "2147483000",
15
+ overflow: "hidden",
16
+ backgroundColor: d.background,
17
+ pointerEvents: "none",
18
+ contain: "strict"
19
+ });
20
+ for (const [o, e, r, i, x, m, w] of d.boxes) {
21
+ const p = s.createElement("div");
22
+ Object.assign(p.style, {
23
+ position: "absolute",
24
+ left: `${o}px`,
25
+ top: `${e}px`,
26
+ width: `${r}px`,
27
+ height: `${i}px`,
28
+ borderRadius: `${x}px`,
29
+ backgroundColor: m,
30
+ border: `1px solid ${w}`,
31
+ boxSizing: "border-box"
32
+ }), l.append(p);
33
+ }
34
+ s.body.append(l);
35
+ }
36
+ }, v = new MutationObserver(() => {
37
+ s.body && (v.disconnect(), M());
38
+ });
39
+ s.body ? M() : d && v.observe(s.documentElement, { childList: !0 });
40
+ const y = () => {
41
+ v.disconnect();
42
+ for (const [o, e, r] of d?.scroll ?? []) {
43
+ const i = s.querySelector(`[data-floe-reload-scroll="${o}"]`);
44
+ i && (i.scrollLeft = e, i.scrollTop = r);
45
+ }
46
+ d = void 0, l?.remove(), l = void 0;
47
+ }, N = () => {
48
+ a = void 0, y();
49
+ try {
50
+ sessionStorage.removeItem(h.storageKey);
51
+ } catch {
52
+ }
53
+ }, O = () => {
54
+ if (!a?.isConnected || l) return;
55
+ const o = [], e = getComputedStyle(s.body).backgroundColor;
56
+ if (!g(e)) return;
57
+ const r = (n) => {
58
+ const t = n.getBoundingClientRect(), c = getComputedStyle(n);
59
+ return t.width && t.height && t.bottom > 0 && t.right > 0 && t.top < innerHeight && t.left < innerWidth && c.visibility === "visible" ? t : null;
60
+ }, i = (n, t, c = "transparent", u = 3) => {
61
+ if (o.length >= b || !g(t) || !g(c)) return;
62
+ const f = Math.max(0, n.x), R = Math.max(0, n.y), k = Math.min(innerWidth, n.right) - f, E = Math.min(innerHeight, n.bottom) - R;
63
+ k <= 0 || E <= 0 || o.push([f, R, k, E, Math.max(0, Math.min(u, 100)), t, c]);
64
+ };
65
+ for (const n of a.querySelectorAll(C)) {
66
+ const t = r(n);
67
+ if (!t) continue;
68
+ const c = getComputedStyle(n);
69
+ i(t, c.backgroundColor, c.borderTopStyle === "none" ? "transparent" : c.borderTopColor, parseFloat(c.borderRadius) || 0);
70
+ }
71
+ const x = getComputedStyle(a).color, m = g(x) ? `color-mix(in srgb, ${x} 9%, ${e})` : "#d4d4d4";
72
+ for (const n of a.querySelectorAll("img,svg,input,textarea,canvas,video")) {
73
+ const t = r(n);
74
+ t && i(t, m, "transparent", Math.min(t.height / 4, 8));
75
+ }
76
+ const w = s.createTreeWalker(a, NodeFilter.SHOW_TEXT), p = s.createRange();
77
+ let S, T = 0;
78
+ for (; (S = w.nextNode()) && o.length < b && T++ < 12e3; ) {
79
+ const n = S.parentElement;
80
+ if (!(!n || n.closest('script,style,textarea,svg,[aria-hidden="true"]') || !r(n))) {
81
+ p.selectNodeContents(S);
82
+ for (const t of p.getClientRects())
83
+ t.width < 2 || i(new DOMRect(t.x, t.y + t.height * 0.22, t.width, t.height * 0.56), m);
84
+ }
85
+ }
86
+ if (o.length)
87
+ try {
88
+ const n = [];
89
+ for (const u of a.querySelectorAll("[data-floe-reload-scroll]")) {
90
+ const f = u.getAttribute("data-floe-reload-scroll");
91
+ /^[a-z0-9-]{1,48}$/.test(f) && n.length < 12 && r(u) && n.push([f, u.scrollLeft, u.scrollTop]);
92
+ }
93
+ const t = { version: 1, scope: A(), width: innerWidth, height: innerHeight, background: e, boxes: o, scroll: n }, c = JSON.stringify(t);
94
+ c.length <= $ && sessionStorage.setItem(h.storageKey, c);
95
+ } catch {
96
+ }
97
+ };
98
+ window.__floeReloadPlaceholder = {
99
+ active: () => !!d,
100
+ restrictTo(o) {
101
+ if (!l) return;
102
+ const e = o.getBoundingClientRect();
103
+ l.style.clipPath = `inset(${Math.max(0, e.top)}px ${Math.max(0, innerWidth - e.right)}px ${Math.max(0, innerHeight - e.bottom)}px ${Math.max(0, e.left)}px)`;
104
+ },
105
+ finish: y,
106
+ arm(o, e) {
107
+ a = o, C = e;
108
+ },
109
+ clear: N
110
+ }, window.addEventListener("pagehide", O), window.addEventListener("resize", y, { once: !0 }), window.addEventListener("pageshow", (o) => {
111
+ o.persisted && y();
112
+ });
113
+ }
114
+ function W(h) {
115
+ const s = JSON.stringify(h).replaceAll("<", "\\u003c").replaceAll("\u2028", "\\u2028").replaceAll("\u2029", "\\u2029");
116
+ return `;(${P.toString()})(${s});`;
117
+ }
118
+ function H() {
119
+ return typeof window > "u" ? void 0 : window.__floeReloadPlaceholder;
120
+ }
121
+ export {
122
+ W as createReloadPlaceholderScript,
123
+ H as getReloadPlaceholder
124
+ };
@@ -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.69.0",
3
+ "version": "0.71.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -108,10 +108,19 @@
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",
114
115
  "default": "./dist/window-status.js"
116
+ },
117
+ "./reload-placeholder": {
118
+ "types": "./dist/reload-placeholder.d.ts",
119
+ "import": "./dist/reload-placeholder.js"
120
+ },
121
+ "./remote-input": {
122
+ "types": "./dist/remote-input.d.ts",
123
+ "import": "./dist/remote-input.js"
115
124
  }
116
125
  },
117
126
  "files": [