@floegence/floe-webapp-core 0.86.31 → 0.86.33
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/dist/chat.css +36 -0
- package/dist/components/chat/blocks/HtmlPreviewCanvas.d.ts +16 -0
- package/dist/components/chat/blocks/HtmlPreviewCanvas.js +153 -0
- package/dist/components/chat/blocks/MarkdownMedia.d.ts +17 -0
- package/dist/components/chat/blocks/MarkdownMedia.js +309 -216
- package/dist/components/html-preview/SandboxedHtmlFrame.d.ts +18 -0
- package/dist/components/html-preview/SandboxedHtmlFrame.js +118 -0
- package/dist/html-preview.d.ts +1 -0
- package/dist/html-preview.js +4 -0
- package/dist/styles.css +1 -1
- package/package.json +5 -1
package/dist/chat.css
CHANGED
|
@@ -1887,3 +1887,39 @@ pre.chat-md-code-block *::selection,
|
|
|
1887
1887
|
@media (prefers-reduced-motion: reduce) { .chat-media-actions, .chat-media-action { transition: none; } }
|
|
1888
1888
|
@media (max-width: 480px) { .chat-media[data-media-kind="html"] .chat-media-viewport, .chat-media-html { height: 18rem; } .chat-media-expanded[data-media-kind="html"] .chat-media-viewport { height: min(70vh, 48rem); } }
|
|
1889
1889
|
}
|
|
1890
|
+
|
|
1891
|
+
/* HTML outputs retain one quiet, bounded canvas throughout their lifecycle. */
|
|
1892
|
+
@layer components {
|
|
1893
|
+
.chat-html-canvas { --canvas-inset: clamp(1rem, 5%, 3rem); position: relative; isolation: isolate; display: block; width: 100%; height: 28rem; overflow: hidden; border: 1px solid color-mix(in srgb, var(--border) 65%, transparent); border-radius: 1rem; background: color-mix(in srgb, var(--muted) 38%, var(--background)); }
|
|
1894
|
+
.chat-media-expanded .chat-html-canvas { height: min(78vh, 52rem); }
|
|
1895
|
+
.chat-html-dots, .chat-html-sheen { position: absolute; inset: 0; pointer-events: none; border-radius: inherit; }
|
|
1896
|
+
.chat-html-dots { background-image: radial-gradient(circle, color-mix(in srgb, var(--muted-foreground) 24%, transparent) .75px, transparent .9px); background-size: 20px 20px; background-position: 9px 9px; }
|
|
1897
|
+
.chat-html-sheen { opacity: 0; background: linear-gradient(115deg, transparent 25%, color-mix(in srgb, var(--foreground) 20%, transparent) 49%, transparent 73%); background-size: 250% 100%; mask-image: radial-gradient(circle, black .9px, transparent 1.1px); mask-size: 20px 20px; mask-position: 9px 9px; }
|
|
1898
|
+
.chat-html-canvas:is([data-state="generating"], [data-state="loading"]) .chat-html-sheen { opacity: 1; animation: chat-html-sweep 3.4s ease-in-out infinite; }
|
|
1899
|
+
.chat-html-sheet, .chat-html-source { position: absolute; inset: 3.5rem var(--canvas-inset) 4.25rem; display: flex; min-width: 0; min-height: 0; overflow: hidden; border: 1px solid color-mix(in srgb, var(--border) 75%, transparent); border-radius: .625rem; box-shadow: 0 14px 40px rgb(0 0 0 / .06), 0 2px 6px rgb(0 0 0 / .03); background: var(--background); }
|
|
1900
|
+
.chat-html-sheet { transition: opacity 280ms ease; }
|
|
1901
|
+
.chat-html-sheet-hidden { opacity: 0; visibility: hidden; pointer-events: none; }
|
|
1902
|
+
.chat-html-sheet .chat-media-html { height: 100%; border-radius: 0; }
|
|
1903
|
+
.chat-html-source { display: block; margin: 0; padding: 1.25rem; overflow: auto; overscroll-behavior: contain; white-space: pre; font: .6875rem/1.85 var(--font-mono); color: var(--foreground); }
|
|
1904
|
+
.chat-html-source code { padding: 0; color: inherit; background: none; font: inherit; }
|
|
1905
|
+
.chat-html-loading { position: absolute; inset: 3.5rem var(--canvas-inset) 4.25rem; display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 1rem; text-align: center; color: var(--muted-foreground); font-size: .75rem; }
|
|
1906
|
+
.chat-html-skeleton { display: flex; flex-direction: column; justify-content: center; gap: .875rem; width: min(72%, 19rem); height: 11rem; padding: 1.75rem; border: 1px solid color-mix(in srgb, var(--border) 60%, transparent); border-radius: .625rem; background: color-mix(in srgb, var(--background) 65%, transparent); }
|
|
1907
|
+
.chat-html-skeleton > span { height: .375rem; border-radius: 1rem; background: color-mix(in srgb, var(--muted-foreground) 12%, transparent); }
|
|
1908
|
+
.chat-html-skeleton > span:first-child { width: 35%; height: .5rem; margin-bottom: .75rem; }
|
|
1909
|
+
.chat-html-skeleton > span:nth-child(3) { width: 82%; }
|
|
1910
|
+
.chat-html-skeleton > span:last-child { width: 60%; }
|
|
1911
|
+
.chat-html-orb { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 2.125rem; height: 2.125rem; padding: 0; border: 1px solid color-mix(in srgb, var(--border) 80%, transparent); border-radius: 50%; color: var(--muted-foreground); background: color-mix(in srgb, var(--background) 88%, transparent); box-shadow: 0 2px 5px rgb(0 0 0 / .035); backdrop-filter: blur(12px); cursor: pointer; transition: color 140ms, background 140ms, transform 140ms; }
|
|
1912
|
+
.chat-html-orb:hover { color: var(--foreground); background: var(--background); transform: translateY(-1px); }
|
|
1913
|
+
.chat-html-orb[aria-pressed="true"] { color: var(--foreground); background: var(--muted); }
|
|
1914
|
+
.chat-html-orb:focus-visible, .chat-html-source:focus-visible { outline: 2px solid var(--ring); outline-offset: 3px; }
|
|
1915
|
+
.chat-html-orb svg { width: .9375rem; height: .9375rem; }
|
|
1916
|
+
.chat-html-expand { position: absolute; top: .875rem; right: .875rem; }
|
|
1917
|
+
.chat-html-controls { position: absolute; bottom: .875rem; left: .875rem; display: flex; align-items: center; gap: .5rem; }
|
|
1918
|
+
.chat-html-feedback { position: absolute; right: 1rem; bottom: 1.125rem; max-width: calc(100% - 7.5rem); display: flex; align-items: center; gap: .4rem; font-size: .6875rem; line-height: 1.5; color: var(--muted-foreground); }
|
|
1919
|
+
.chat-html-feedback svg { width: .875rem; height: .875rem; flex-shrink: 0; }
|
|
1920
|
+
@keyframes chat-html-sweep { from { background-position: 120% 0; } to { background-position: -80% 0; } }
|
|
1921
|
+
@media (max-width: 480px) { .chat-html-canvas { height: 25rem; --canvas-inset: 1rem; } }
|
|
1922
|
+
@media (pointer: coarse) { .chat-html-orb { width: 2.625rem; height: 2.625rem; } .chat-html-controls { bottom: .75rem; left: .75rem; } }
|
|
1923
|
+
@media (prefers-reduced-motion: reduce) { .chat-html-sheen { animation: none !important; background-position: 50% 0; } .chat-html-orb, .chat-html-sheet { transition: none; } }
|
|
1924
|
+
@media (forced-colors: active) { .chat-html-orb, .chat-html-source, .chat-html-canvas { border-color: CanvasText; } .chat-html-sheen { display: none; } }
|
|
1925
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type Component, type JSX } from 'solid-js';
|
|
2
|
+
import type { MarkdownMediaLabels, ResolvedMarkdownMedia } from './MarkdownMedia';
|
|
3
|
+
export interface HtmlPreviewCanvasProps {
|
|
4
|
+
state: 'loading' | 'generating' | 'incomplete' | 'ready' | 'error';
|
|
5
|
+
html: string;
|
|
6
|
+
labels: MarkdownMediaLabels;
|
|
7
|
+
expanded: boolean;
|
|
8
|
+
onExpand: () => void;
|
|
9
|
+
onRetry: () => void;
|
|
10
|
+
onPreview: () => void;
|
|
11
|
+
previewLabel: string;
|
|
12
|
+
resource?: ResolvedMarkdownMedia;
|
|
13
|
+
children: JSX.Element;
|
|
14
|
+
}
|
|
15
|
+
/** One bounded surface owns generation, source inspection, and the isolated preview. */
|
|
16
|
+
export declare const HtmlPreviewCanvas: Component<HtmlPreviewCanvasProps>;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { createComponent as a, insert as l, effect as F, setAttribute as r, memo as N, template as u, use as O, delegateEvents as j } from "solid-js/web";
|
|
2
|
+
import { createSignal as U, onCleanup as B, createMemo as I, Show as d } from "solid-js";
|
|
3
|
+
import { Copy as J, Eye as z, Refresh as D, ExternalLink as K, FolderOpen as Q, Minus as W, Maximize as X, Code as Y, MoreHorizontal as Z, Check as p } from "../../icons/index.js";
|
|
4
|
+
import { Dropdown as ee } from "../../ui/Dropdown.js";
|
|
5
|
+
var te = /* @__PURE__ */ u("<pre class=chat-html-source tabindex=0><code>"), ae = /* @__PURE__ */ u("<span class=chat-html-skeleton aria-hidden=true><span></span><span></span><span></span><span>"), le = /* @__PURE__ */ u("<button type=button class=chat-media-retry>"), ne = /* @__PURE__ */ u("<span class=chat-html-loading role=status><span>"), re = /* @__PURE__ */ u("<span class=chat-html-feedback role=status>"), ie = /* @__PURE__ */ u('<span class=chat-html-canvas><span class=chat-html-dots aria-hidden=true></span><span class=chat-html-sheen aria-hidden=true></span><span class=chat-html-sheet></span><button type=button class="chat-html-orb chat-html-expand"></button><span class=chat-html-controls><button type=button class=chat-html-orb data-chat-media-action=source>');
|
|
6
|
+
const ue = (e) => {
|
|
7
|
+
const [c, y] = U(!1), [x, S] = U("");
|
|
8
|
+
let b, v = !1, f;
|
|
9
|
+
const k = (n) => {
|
|
10
|
+
clearTimeout(f), S(n), f = setTimeout(() => S(""), 2400);
|
|
11
|
+
};
|
|
12
|
+
B(() => {
|
|
13
|
+
v = !0, clearTimeout(f);
|
|
14
|
+
});
|
|
15
|
+
const V = async () => {
|
|
16
|
+
try {
|
|
17
|
+
if (await navigator.clipboard.writeText(e.html), v) return;
|
|
18
|
+
k(e.labels.copied ?? "Copied");
|
|
19
|
+
} catch {
|
|
20
|
+
if (v) return;
|
|
21
|
+
const n = e.labels.copyFailed ?? "Select and copy the source.";
|
|
22
|
+
y(!0), queueMicrotask(() => {
|
|
23
|
+
if (v) return;
|
|
24
|
+
b.focus({
|
|
25
|
+
preventScroll: !0
|
|
26
|
+
});
|
|
27
|
+
const g = document.createRange();
|
|
28
|
+
g.selectNodeContents(b);
|
|
29
|
+
const m = window.getSelection();
|
|
30
|
+
m?.removeAllRanges(), m?.addRange(g), k(n);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}, q = I(() => [{
|
|
34
|
+
id: "copy",
|
|
35
|
+
label: e.labels.copyHTML ?? "Copy HTML",
|
|
36
|
+
icon: () => a(J, {}),
|
|
37
|
+
disabled: !e.html
|
|
38
|
+
}, ...e.state === "ready" ? [{
|
|
39
|
+
id: "preview",
|
|
40
|
+
label: e.previewLabel,
|
|
41
|
+
icon: () => a(z, {})
|
|
42
|
+
}, {
|
|
43
|
+
id: "replay",
|
|
44
|
+
label: e.labels.replay ?? "Reload preview",
|
|
45
|
+
icon: () => a(D, {})
|
|
46
|
+
}] : [], ...e.resource?.openURL && !e.resource.reveal ? [{
|
|
47
|
+
id: "open",
|
|
48
|
+
label: e.labels.open,
|
|
49
|
+
icon: () => a(K, {})
|
|
50
|
+
}] : [], ...e.resource?.reveal ? [{
|
|
51
|
+
id: "reveal",
|
|
52
|
+
label: e.resource.reveal.label,
|
|
53
|
+
icon: () => a(Q, {})
|
|
54
|
+
}] : []]), G = () => e.state === "generating" ? e.labels.generating ?? e.labels.loading : e.state === "incomplete" ? e.labels.incomplete ?? "Generation interrupted. Source is available." : e.state === "error" ? e.labels.unavailable : e.labels.loading;
|
|
55
|
+
return (() => {
|
|
56
|
+
var n = ie(), g = n.firstChild, m = g.nextSibling, w = m.nextSibling, s = w.nextSibling, $ = s.nextSibling, h = $.firstChild;
|
|
57
|
+
return n.$$keydown = (t) => {
|
|
58
|
+
t.key === "Escape" && e.expanded && !t.defaultPrevented && (t.preventDefault(), e.onExpand());
|
|
59
|
+
}, l(w, () => e.children), l(n, a(d, {
|
|
60
|
+
get when() {
|
|
61
|
+
return c();
|
|
62
|
+
},
|
|
63
|
+
get children() {
|
|
64
|
+
var t = te(), o = t.firstChild, i = b;
|
|
65
|
+
return typeof i == "function" ? O(i, t) : b = t, l(o, () => e.html), F(() => r(t, "aria-label", e.labels.showSource ?? "HTML source")), t;
|
|
66
|
+
}
|
|
67
|
+
}), s), l(n, a(d, {
|
|
68
|
+
get when() {
|
|
69
|
+
return N(() => !c())() && e.state !== "ready";
|
|
70
|
+
},
|
|
71
|
+
get children() {
|
|
72
|
+
var t = ne(), o = t.firstChild;
|
|
73
|
+
return l(t, a(d, {
|
|
74
|
+
get when() {
|
|
75
|
+
return e.state === "loading" || e.state === "generating";
|
|
76
|
+
},
|
|
77
|
+
get children() {
|
|
78
|
+
return ae();
|
|
79
|
+
}
|
|
80
|
+
}), o), l(o, G), l(t, a(d, {
|
|
81
|
+
get when() {
|
|
82
|
+
return e.state === "error";
|
|
83
|
+
},
|
|
84
|
+
get children() {
|
|
85
|
+
var i = le();
|
|
86
|
+
return i.$$click = () => e.onRetry(), l(i, a(D, {}), null), l(i, () => e.labels.retry, null), i;
|
|
87
|
+
}
|
|
88
|
+
}), null), t;
|
|
89
|
+
}
|
|
90
|
+
}), s), s.$$click = () => e.onExpand(), l(s, a(d, {
|
|
91
|
+
get when() {
|
|
92
|
+
return e.expanded;
|
|
93
|
+
},
|
|
94
|
+
get fallback() {
|
|
95
|
+
return a(X, {});
|
|
96
|
+
},
|
|
97
|
+
get children() {
|
|
98
|
+
return a(W, {});
|
|
99
|
+
}
|
|
100
|
+
})), h.$$click = () => y((t) => !t), l(h, a(d, {
|
|
101
|
+
get when() {
|
|
102
|
+
return c();
|
|
103
|
+
},
|
|
104
|
+
get fallback() {
|
|
105
|
+
return a(Y, {});
|
|
106
|
+
},
|
|
107
|
+
get children() {
|
|
108
|
+
return a(z, {});
|
|
109
|
+
}
|
|
110
|
+
})), l($, a(ee, {
|
|
111
|
+
get trigger() {
|
|
112
|
+
return a(Z, {});
|
|
113
|
+
},
|
|
114
|
+
triggerClass: "chat-html-orb",
|
|
115
|
+
get triggerAriaLabel() {
|
|
116
|
+
return e.labels.more ?? "More actions";
|
|
117
|
+
},
|
|
118
|
+
items: q,
|
|
119
|
+
align: "start",
|
|
120
|
+
onSelect: (t) => {
|
|
121
|
+
t === "copy" ? V() : t === "preview" ? e.onPreview() : t === "replay" ? e.onRetry() : t === "reveal" ? e.resource?.reveal?.onSelect() : t === "open" && e.resource?.openURL && window.open(e.resource.openURL, "_blank", "noopener,noreferrer");
|
|
122
|
+
}
|
|
123
|
+
}), null), l(n, a(d, {
|
|
124
|
+
get when() {
|
|
125
|
+
return x();
|
|
126
|
+
},
|
|
127
|
+
get children() {
|
|
128
|
+
var t = re();
|
|
129
|
+
return l(t, a(p, {}), null), l(t, x, null), t;
|
|
130
|
+
}
|
|
131
|
+
}), null), F((t) => {
|
|
132
|
+
var o = e.state, i = c() ? "source" : "preview", C = e.state === "generating" || e.state === "loading", L = !!(c() || e.state !== "ready"), M = c() || e.state !== "ready", R = e.expanded ? "collapse" : "expand", T = e.expanded, _ = e.expanded ? e.labels.collapse : e.labels.expand, H = e.expanded ? e.labels.collapse : e.labels.expand, E = c(), P = c() ? e.labels.showPreview ?? "Show preview" : e.labels.showSource ?? "Show HTML source", A = c() ? e.labels.showPreview ?? "Show preview" : e.labels.showSource ?? "Show HTML source";
|
|
133
|
+
return o !== t.e && r(n, "data-state", t.e = o), i !== t.t && r(n, "data-view", t.t = i), C !== t.a && r(n, "aria-busy", t.a = C), L !== t.o && w.classList.toggle("chat-html-sheet-hidden", t.o = L), M !== t.i && (w.inert = t.i = M), R !== t.n && r(s, "data-chat-media-action", t.n = R), T !== t.s && r(s, "aria-expanded", t.s = T), _ !== t.h && r(s, "aria-label", t.h = _), H !== t.r && r(s, "title", t.r = H), E !== t.d && r(h, "aria-pressed", t.d = E), P !== t.l && r(h, "aria-label", t.l = P), A !== t.u && r(h, "title", t.u = A), t;
|
|
134
|
+
}, {
|
|
135
|
+
e: void 0,
|
|
136
|
+
t: void 0,
|
|
137
|
+
a: void 0,
|
|
138
|
+
o: void 0,
|
|
139
|
+
i: void 0,
|
|
140
|
+
n: void 0,
|
|
141
|
+
s: void 0,
|
|
142
|
+
h: void 0,
|
|
143
|
+
r: void 0,
|
|
144
|
+
d: void 0,
|
|
145
|
+
l: void 0,
|
|
146
|
+
u: void 0
|
|
147
|
+
}), n;
|
|
148
|
+
})();
|
|
149
|
+
};
|
|
150
|
+
j(["keydown", "click"]);
|
|
151
|
+
export {
|
|
152
|
+
ue as HtmlPreviewCanvas
|
|
153
|
+
};
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { type Component } from 'solid-js';
|
|
2
2
|
import { type MarkdownMediaSource } from '../markdown/media';
|
|
3
3
|
export interface MarkdownMediaLabels {
|
|
4
|
+
generating?: string;
|
|
5
|
+
incomplete?: string;
|
|
6
|
+
showSource?: string;
|
|
7
|
+
showPreview?: string;
|
|
8
|
+
more?: string;
|
|
9
|
+
copyHTML?: string;
|
|
10
|
+
copied?: string;
|
|
11
|
+
copyFailed?: string;
|
|
12
|
+
replay?: string;
|
|
4
13
|
image: string;
|
|
5
14
|
/** Label for opening an image in its preview window. */
|
|
6
15
|
previewImage?: string;
|
|
@@ -9,6 +18,10 @@ export interface MarkdownMediaLabels {
|
|
|
9
18
|
html: string;
|
|
10
19
|
/** Label for opening an HTML file in the host's file preview surface. */
|
|
11
20
|
previewHTML?: string;
|
|
21
|
+
/** Label for an independent floating preview of inline HTML. */
|
|
22
|
+
previewHTMLWindow?: string;
|
|
23
|
+
linkUnavailable?: string;
|
|
24
|
+
anchorUnavailable?: string;
|
|
12
25
|
loading: string;
|
|
13
26
|
unavailable: string;
|
|
14
27
|
retry: string;
|
|
@@ -36,9 +49,13 @@ export interface ResolvedMarkdownMedia {
|
|
|
36
49
|
}
|
|
37
50
|
export interface MarkdownMediaProps {
|
|
38
51
|
source: MarkdownMediaSource;
|
|
52
|
+
/** Partial HTML remains inspectable but never executes before a closed fence. */
|
|
53
|
+
htmlState?: 'generating' | 'incomplete';
|
|
39
54
|
labels: MarkdownMediaLabels;
|
|
40
55
|
/** Replace this callback when its authorization or resource context changes. Reads inside it do not trigger reloads. */
|
|
41
56
|
resolve?: (source: MarkdownMediaSource, signal: AbortSignal) => Promise<ResolvedMarkdownMedia>;
|
|
57
|
+
/** Resolve and authorize a user-activated HTML link using the source's host context. */
|
|
58
|
+
onOpenLink?: (source: MarkdownMediaSource, href: string, signal: AbortSignal) => void | Promise<void>;
|
|
42
59
|
class?: string;
|
|
43
60
|
}
|
|
44
61
|
/** Stable inline media with host-owned resource resolution and an isolated HTML document. */
|