sgiant-ai-widget 1.0.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.
- package/LICENSE +21 -0
- package/README.md +155 -0
- package/dist/ai-invalidation.d.ts +29 -0
- package/dist/ai-invalidation.d.ts.map +1 -0
- package/dist/ai-invalidation.js +121 -0
- package/dist/ai-invalidation.js.map +1 -0
- package/dist/contrast.d.ts +87 -0
- package/dist/contrast.d.ts.map +1 -0
- package/dist/contrast.js +151 -0
- package/dist/contrast.js.map +1 -0
- package/dist/directive.d.ts +37 -0
- package/dist/directive.d.ts.map +1 -0
- package/dist/directive.js +87 -0
- package/dist/directive.js.map +1 -0
- package/dist/dom.d.ts +83 -0
- package/dist/dom.d.ts.map +1 -0
- package/dist/dom.js +146 -0
- package/dist/dom.js.map +1 -0
- package/dist/host-actions.d.ts +192 -0
- package/dist/host-actions.d.ts.map +1 -0
- package/dist/host-actions.js +443 -0
- package/dist/host-actions.js.map +1 -0
- package/dist/icons.d.ts +21 -0
- package/dist/icons.d.ts.map +1 -0
- package/dist/icons.js +84 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +600 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4833 -0
- package/dist/index.js.map +1 -0
- package/dist/labels.d.ts +187 -0
- package/dist/labels.d.ts.map +1 -0
- package/dist/labels.js +232 -0
- package/dist/labels.js.map +1 -0
- package/dist/limits.d.ts +43 -0
- package/dist/limits.d.ts.map +1 -0
- package/dist/limits.js +48 -0
- package/dist/limits.js.map +1 -0
- package/dist/markdown.d.ts +23 -0
- package/dist/markdown.d.ts.map +1 -0
- package/dist/markdown.js +0 -0
- package/dist/markdown.js.map +1 -0
- package/dist/message-chrome.d.ts +45 -0
- package/dist/message-chrome.d.ts.map +1 -0
- package/dist/message-chrome.js +101 -0
- package/dist/message-chrome.js.map +1 -0
- package/dist/pane-follow.d.ts +67 -0
- package/dist/pane-follow.d.ts.map +1 -0
- package/dist/pane-follow.js +61 -0
- package/dist/pane-follow.js.map +1 -0
- package/dist/prefix.d.ts +9 -0
- package/dist/prefix.d.ts.map +1 -0
- package/dist/prefix.js +9 -0
- package/dist/prefix.js.map +1 -0
- package/dist/proposal-summary.d.ts +19 -0
- package/dist/proposal-summary.d.ts.map +1 -0
- package/dist/proposal-summary.js +73 -0
- package/dist/proposal-summary.js.map +1 -0
- package/dist/replay.d.ts +125 -0
- package/dist/replay.d.ts.map +1 -0
- package/dist/replay.js +172 -0
- package/dist/replay.js.map +1 -0
- package/dist/safe-url.d.ts +47 -0
- package/dist/safe-url.d.ts.map +1 -0
- package/dist/safe-url.js +80 -0
- package/dist/safe-url.js.map +1 -0
- package/dist/sgiant-ai-widget.global.js +778 -0
- package/dist/sgiant-ai-widget.global.js.map +7 -0
- package/dist/specs.d.ts +173 -0
- package/dist/specs.d.ts.map +1 -0
- package/dist/specs.js +231 -0
- package/dist/specs.js.map +1 -0
- package/dist/storage.d.ts +77 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +124 -0
- package/dist/storage.js.map +1 -0
- package/dist/styles.d.ts +29 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/styles.js +876 -0
- package/dist/styles.js.map +1 -0
- package/dist/ui-control.d.ts +8 -0
- package/dist/ui-control.d.ts.map +1 -0
- package/dist/ui-control.js +8 -0
- package/dist/ui-control.js.map +1 -0
- package/dist/ui-render.d.ts +75 -0
- package/dist/ui-render.d.ts.map +1 -0
- package/dist/ui-render.js +617 -0
- package/dist/ui-render.js.map +1 -0
- package/dist/ui-spec.d.ts +143 -0
- package/dist/ui-spec.d.ts.map +1 -0
- package/dist/ui-spec.js +288 -0
- package/dist/ui-spec.js.map +1 -0
- package/package.json +61 -0
package/dist/specs.d.ts
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The directive vocabulary: what the model may emit inside `[[tag:{json}]]`,
|
|
3
|
+
* and the parsing that turns it into something a renderer can draw.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from index.ts (#320) at zero leakage. This is the half of the
|
|
6
|
+
* widget's extension seam that #306 calls "a real extension point" — the model
|
|
7
|
+
* emits a directive, the host registers a renderer, the widget hands over the
|
|
8
|
+
* parsed JSON. A third party writing their own renderer needs these shapes,
|
|
9
|
+
* and needs to find them without reading 7000 lines.
|
|
10
|
+
*
|
|
11
|
+
* `stripDirectivesForReplay` lives here rather than beside the replay code
|
|
12
|
+
* because it is the same grammar read in the other direction: it removes
|
|
13
|
+
* exactly what this file defines.
|
|
14
|
+
*/
|
|
15
|
+
/** A data widget the assistant can render inline via `[[widget:{json}]]`. */
|
|
16
|
+
export interface WidgetSpec {
|
|
17
|
+
/** "stat" | "kpis" | "list" | "table". Unknown kinds fall back to a list. */
|
|
18
|
+
kind?: string;
|
|
19
|
+
title?: string;
|
|
20
|
+
/** stat: the big value + caption + optional delta. */
|
|
21
|
+
value?: string | number;
|
|
22
|
+
caption?: string;
|
|
23
|
+
delta?: string;
|
|
24
|
+
/** kpis: tiles of {label,value}. */
|
|
25
|
+
items?: Array<{
|
|
26
|
+
label?: string;
|
|
27
|
+
value?: string | number;
|
|
28
|
+
delta?: string;
|
|
29
|
+
}>;
|
|
30
|
+
/** list: plain bullet lines. */
|
|
31
|
+
lines?: string[];
|
|
32
|
+
/** table: header columns + row cells. */
|
|
33
|
+
columns?: string[];
|
|
34
|
+
rows?: Array<Array<string | number>>;
|
|
35
|
+
}
|
|
36
|
+
/** A navigation suggestion the assistant emits via `[[navigate:{json}]]`. */
|
|
37
|
+
export interface NavigateSpec {
|
|
38
|
+
path: string;
|
|
39
|
+
label?: string;
|
|
40
|
+
}
|
|
41
|
+
/** A dynamic HTML preview the assistant draws via `[[preview:{json}]]` — rendered
|
|
42
|
+
* in a fully sandboxed iframe (no scripts, no same-origin) so arbitrary HTML+CSS
|
|
43
|
+
* paints the real look but nothing can execute or reach out. */
|
|
44
|
+
export interface PreviewSpec {
|
|
45
|
+
html: string;
|
|
46
|
+
title?: string;
|
|
47
|
+
}
|
|
48
|
+
/** An in-app action the assistant proposes via `[[action:{json}]]`. The host
|
|
49
|
+
* maps `name` to a real operation; `confirm` (if set) requires user approval. */
|
|
50
|
+
export interface ActionSpec {
|
|
51
|
+
name: string;
|
|
52
|
+
label?: string;
|
|
53
|
+
/** Confirmation prompt — when set, the user must approve before it runs. */
|
|
54
|
+
confirm?: string;
|
|
55
|
+
/** Opaque data passed to the host's onWidgetAction(name, data). */
|
|
56
|
+
data?: Record<string, string>;
|
|
57
|
+
}
|
|
58
|
+
/** Quick-reply chips the assistant offers via `[[chips:{json}]]` — tappable
|
|
59
|
+
* answer options so the user picks instead of typing. Single-select sends on
|
|
60
|
+
* tap; multi-select toggles + a Send button; `other` adds a "type your own"
|
|
61
|
+
* chip. The chosen text is sent as a NORMAL message (history stays in order). */
|
|
62
|
+
export interface ChipsSpec {
|
|
63
|
+
options: string[];
|
|
64
|
+
multi?: boolean;
|
|
65
|
+
other?: boolean;
|
|
66
|
+
}
|
|
67
|
+
/** Sentinel the assistant emits to ask the widget to render an email form. */
|
|
68
|
+
export declare const LEAD_TOKEN = "[[collect-email]]";
|
|
69
|
+
/** One field in an AI-rendered form directive. */
|
|
70
|
+
export interface FormField {
|
|
71
|
+
name: string;
|
|
72
|
+
label?: string;
|
|
73
|
+
/** Kept in step with what `buildField` can actually draw — a spec allowed to
|
|
74
|
+
* ask for a control the builder cannot render is a promise to the model that
|
|
75
|
+
* the UI then breaks. */
|
|
76
|
+
type?: "text" | "email" | "number" | "textarea" | "select" | "checkbox" | "radio";
|
|
77
|
+
placeholder?: string;
|
|
78
|
+
required?: boolean;
|
|
79
|
+
options?: string[];
|
|
80
|
+
}
|
|
81
|
+
export interface FormSpec {
|
|
82
|
+
action: string;
|
|
83
|
+
title?: string;
|
|
84
|
+
fields: FormField[];
|
|
85
|
+
submit?: string;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* A field the ASSISTANT asked the user to fill in on a proposal card.
|
|
89
|
+
*
|
|
90
|
+
* Declared by the proposal, never by the widget. A table of "which args are
|
|
91
|
+
* editable for which tool" hardcoded in the UI would mean the chat can only
|
|
92
|
+
* ever ask the questions the frontend was built to ask — a new tool, or a
|
|
93
|
+
* decision the model wants confirmed, would need a UI release. The model knows
|
|
94
|
+
* what it is unsure about; it says so, and the card renders it.
|
|
95
|
+
*
|
|
96
|
+
* Same shape as a `[[form:…]]` field, and built by the same `buildField`, so an
|
|
97
|
+
* input looks identical wherever the chat draws one.
|
|
98
|
+
*/
|
|
99
|
+
export interface ProposalField {
|
|
100
|
+
/** The proposal ARG this field overwrites on apply. */
|
|
101
|
+
arg: string;
|
|
102
|
+
label?: string;
|
|
103
|
+
type?: string;
|
|
104
|
+
placeholder?: string;
|
|
105
|
+
options?: string[];
|
|
106
|
+
required?: boolean;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* A control `buildField` produced, plus how to read its answer.
|
|
110
|
+
*
|
|
111
|
+
* The reader is part of the return value because "what the user chose" is not
|
|
112
|
+
* `.value` for every control type — a checkbox is `.checked`, a radio group is
|
|
113
|
+
* whichever of its inputs is checked — and every caller wants the same thing: a
|
|
114
|
+
* string to put in the payload.
|
|
115
|
+
*/
|
|
116
|
+
export interface BuiltField {
|
|
117
|
+
/** Append THIS — the control, or the wrapper a labelled/grouped one needs. */
|
|
118
|
+
node: HTMLElement;
|
|
119
|
+
read: () => string;
|
|
120
|
+
/** The control draws its own caption; a caller adding one would double it. */
|
|
121
|
+
selfLabelled?: boolean;
|
|
122
|
+
}
|
|
123
|
+
/** Truthiness for a prefilled checkbox — model output, so accept the obvious
|
|
124
|
+
* spellings rather than demanding exactly `"true"`. */
|
|
125
|
+
export declare function isTruthyValue(v: string | undefined): boolean;
|
|
126
|
+
/** A proposal arg a field can be PREFILLED from: anything JSON-primitive, which
|
|
127
|
+
* is everything with one obvious rendering. Objects/arrays have none, and
|
|
128
|
+
* null/undefined mean the assistant proposed nothing for that arg. */
|
|
129
|
+
export declare function isPrimitiveArg(v: unknown): v is string | number | boolean;
|
|
130
|
+
/** Read the fields off a proposal frame, defensively — this is model output. */
|
|
131
|
+
export declare function proposalFields(raw: unknown): ProposalField[];
|
|
132
|
+
/** Pull a `[[form:{json}]]` directive out of assistant text, if present. Uses
|
|
133
|
+
* the shared brace-matching extractor, then validates the form shape. */
|
|
134
|
+
export declare function parseFormDirective(text: string): {
|
|
135
|
+
spec: FormSpec;
|
|
136
|
+
stripped: string;
|
|
137
|
+
} | null;
|
|
138
|
+
/**
|
|
139
|
+
* REPLAY view of a stored message: strip every interactive directive (which the
|
|
140
|
+
* live turn already rendered as buttons/forms) so a reopened conversation shows
|
|
141
|
+
* clean prose instead of raw `[[navigate:…]]` / `[[form:…]]` code, with a short
|
|
142
|
+
* INERT note per directive (no re-execution). Used by the history/thread
|
|
143
|
+
* restore path; live turns still render the real interactive widgets.
|
|
144
|
+
*/
|
|
145
|
+
export declare function stripDirectivesForReplay(text: string): {
|
|
146
|
+
clean: string;
|
|
147
|
+
notes: string[];
|
|
148
|
+
navs: NavigateSpec[];
|
|
149
|
+
uis: unknown[];
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* Build one editable control for a proposal form, plus how to READ it back.
|
|
153
|
+
*
|
|
154
|
+
* Moved here from index.ts (#320) with ZERO closure dependencies — the whole
|
|
155
|
+
* function reaches only `el`, `PREFIX`, and this file's own `BuiltField` and
|
|
156
|
+
* `isTruthyValue`. It sat in the 6000-line closure purely because that is
|
|
157
|
+
* where it was typed; nothing about it was ever widget state.
|
|
158
|
+
*
|
|
159
|
+
* `read` is the point of the return shape: every control answers as a STRING
|
|
160
|
+
* whatever it was drawn as, because the submit path posts a flat
|
|
161
|
+
* `Record<string, string>`. A checkbox that returned a boolean would be the
|
|
162
|
+
* one field in the form with a different contract.
|
|
163
|
+
*/
|
|
164
|
+
export declare function buildField(field: {
|
|
165
|
+
name: string;
|
|
166
|
+
type?: string;
|
|
167
|
+
label?: string;
|
|
168
|
+
placeholder?: string;
|
|
169
|
+
options?: string[];
|
|
170
|
+
required?: boolean;
|
|
171
|
+
value?: string;
|
|
172
|
+
}): BuiltField;
|
|
173
|
+
//# sourceMappingURL=specs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"specs.d.ts","sourceRoot":"","sources":["../src/specs.ts"],"names":[],"mappings":"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"}
|
package/dist/specs.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The directive vocabulary: what the model may emit inside `[[tag:{json}]]`,
|
|
3
|
+
* and the parsing that turns it into something a renderer can draw.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from index.ts (#320) at zero leakage. This is the half of the
|
|
6
|
+
* widget's extension seam that #306 calls "a real extension point" — the model
|
|
7
|
+
* emits a directive, the host registers a renderer, the widget hands over the
|
|
8
|
+
* parsed JSON. A third party writing their own renderer needs these shapes,
|
|
9
|
+
* and needs to find them without reading 7000 lines.
|
|
10
|
+
*
|
|
11
|
+
* `stripDirectivesForReplay` lives here rather than beside the replay code
|
|
12
|
+
* because it is the same grammar read in the other direction: it removes
|
|
13
|
+
* exactly what this file defines.
|
|
14
|
+
*/
|
|
15
|
+
import { parseJsonDirective } from "./directive.js";
|
|
16
|
+
import { el } from "./dom.js";
|
|
17
|
+
import { PREFIX } from "./prefix.js";
|
|
18
|
+
/** Sentinel the assistant emits to ask the widget to render an email form. */
|
|
19
|
+
export const LEAD_TOKEN = "[[collect-email]]";
|
|
20
|
+
/** Truthiness for a prefilled checkbox — model output, so accept the obvious
|
|
21
|
+
* spellings rather than demanding exactly `"true"`. */
|
|
22
|
+
export function isTruthyValue(v) {
|
|
23
|
+
return ["true", "1", "yes", "on"].includes((v ?? "").trim().toLowerCase());
|
|
24
|
+
}
|
|
25
|
+
/** A proposal arg a field can be PREFILLED from: anything JSON-primitive, which
|
|
26
|
+
* is everything with one obvious rendering. Objects/arrays have none, and
|
|
27
|
+
* null/undefined mean the assistant proposed nothing for that arg. */
|
|
28
|
+
export function isPrimitiveArg(v) {
|
|
29
|
+
return (typeof v === "string" || typeof v === "number" || typeof v === "boolean");
|
|
30
|
+
}
|
|
31
|
+
/** Read the fields off a proposal frame, defensively — this is model output. */
|
|
32
|
+
export function proposalFields(raw) {
|
|
33
|
+
if (!Array.isArray(raw))
|
|
34
|
+
return [];
|
|
35
|
+
return raw
|
|
36
|
+
.filter((f) => Boolean(f) &&
|
|
37
|
+
typeof f === "object" &&
|
|
38
|
+
typeof f.arg === "string" &&
|
|
39
|
+
Boolean(f.arg))
|
|
40
|
+
.slice(0, 6);
|
|
41
|
+
}
|
|
42
|
+
/** Pull a `[[form:{json}]]` directive out of assistant text, if present. Uses
|
|
43
|
+
* the shared brace-matching extractor, then validates the form shape. */
|
|
44
|
+
export function parseFormDirective(text) {
|
|
45
|
+
const r = parseJsonDirective(text, "form");
|
|
46
|
+
if (!r)
|
|
47
|
+
return null;
|
|
48
|
+
const { spec } = r;
|
|
49
|
+
if (!spec || typeof spec.action !== "string" || !Array.isArray(spec.fields))
|
|
50
|
+
return null;
|
|
51
|
+
return r;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* REPLAY view of a stored message: strip every interactive directive (which the
|
|
55
|
+
* live turn already rendered as buttons/forms) so a reopened conversation shows
|
|
56
|
+
* clean prose instead of raw `[[navigate:…]]` / `[[form:…]]` code, with a short
|
|
57
|
+
* INERT note per directive (no re-execution). Used by the history/thread
|
|
58
|
+
* restore path; live turns still render the real interactive widgets.
|
|
59
|
+
*/
|
|
60
|
+
export function stripDirectivesForReplay(text) {
|
|
61
|
+
let t = text;
|
|
62
|
+
const notes = [];
|
|
63
|
+
const uis = [];
|
|
64
|
+
// Navigation is idempotent and side-effect-free, so on replay we hand it back
|
|
65
|
+
// to be re-rendered as a REAL clickable chip rather than flattened to an inert
|
|
66
|
+
// note — otherwise every "Open <page>" the assistant offered goes dead the
|
|
67
|
+
// moment the thread reloads (which send() does after each turn for canonical
|
|
68
|
+
// ids). This is the true cause of #111: the model DID emit [[navigate]], the
|
|
69
|
+
// restore path is what killed it.
|
|
70
|
+
const navs = [];
|
|
71
|
+
for (let i = 0; i < 8; i++) {
|
|
72
|
+
const w = parseJsonDirective(t, "navigate");
|
|
73
|
+
if (!w)
|
|
74
|
+
break;
|
|
75
|
+
t = w.stripped;
|
|
76
|
+
if (w.spec.path)
|
|
77
|
+
navs.push(w.spec);
|
|
78
|
+
else
|
|
79
|
+
notes.push(`↗ ${w.spec.label || "Open page"}`);
|
|
80
|
+
}
|
|
81
|
+
for (let i = 0; i < 8; i++) {
|
|
82
|
+
const w = parseJsonDirective(t, "action");
|
|
83
|
+
if (!w)
|
|
84
|
+
break;
|
|
85
|
+
t = w.stripped;
|
|
86
|
+
notes.push(`• ${w.spec.label || w.spec.name}`);
|
|
87
|
+
}
|
|
88
|
+
for (let i = 0; i < 8; i++) {
|
|
89
|
+
const w = parseJsonDirective(t, "widget");
|
|
90
|
+
if (!w)
|
|
91
|
+
break;
|
|
92
|
+
t = w.stripped;
|
|
93
|
+
notes.push(`▦ ${w.spec.title || "widget"}`);
|
|
94
|
+
}
|
|
95
|
+
// Composed UI cards are handed BACK to be re-drawn, not flattened to a note.
|
|
96
|
+
// A card is the substance of the turn, not a decoration on it: a client who
|
|
97
|
+
// reopens the conversation tomorrow to look at the scenes they were approving
|
|
98
|
+
// would otherwise find "▦ card" where the scenes had been. Re-drawing is safe
|
|
99
|
+
// because every button on a card requires a click — nothing here can run by
|
|
100
|
+
// itself on restore.
|
|
101
|
+
for (let i = 0; i < 4; i++) {
|
|
102
|
+
const u = parseJsonDirective(t, "ui");
|
|
103
|
+
if (!u)
|
|
104
|
+
break;
|
|
105
|
+
t = u.stripped;
|
|
106
|
+
uis.push(u.spec);
|
|
107
|
+
}
|
|
108
|
+
for (let i = 0; i < 3; i++) {
|
|
109
|
+
const p = parseJsonDirective(t, "preview");
|
|
110
|
+
if (!p)
|
|
111
|
+
break;
|
|
112
|
+
t = p.stripped;
|
|
113
|
+
notes.push(`[preview] ${p.spec.title || "Preview"}`);
|
|
114
|
+
}
|
|
115
|
+
for (let i = 0; i < 4; i++) {
|
|
116
|
+
const c = parseJsonDirective(t, "chips");
|
|
117
|
+
if (!c)
|
|
118
|
+
break;
|
|
119
|
+
t = c.stripped;
|
|
120
|
+
notes.push("💬 options offered");
|
|
121
|
+
}
|
|
122
|
+
const f = parseFormDirective(t);
|
|
123
|
+
if (f) {
|
|
124
|
+
t = f.stripped;
|
|
125
|
+
notes.push(`📝 ${f.spec.title || "Form"} — submitted`);
|
|
126
|
+
}
|
|
127
|
+
if (t.includes(LEAD_TOKEN)) {
|
|
128
|
+
t = t.replace(LEAD_TOKEN, "").trim();
|
|
129
|
+
notes.push("📝 Form — submitted");
|
|
130
|
+
}
|
|
131
|
+
return { clean: t, notes, navs, uis };
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Build one editable control for a proposal form, plus how to READ it back.
|
|
135
|
+
*
|
|
136
|
+
* Moved here from index.ts (#320) with ZERO closure dependencies — the whole
|
|
137
|
+
* function reaches only `el`, `PREFIX`, and this file's own `BuiltField` and
|
|
138
|
+
* `isTruthyValue`. It sat in the 6000-line closure purely because that is
|
|
139
|
+
* where it was typed; nothing about it was ever widget state.
|
|
140
|
+
*
|
|
141
|
+
* `read` is the point of the return shape: every control answers as a STRING
|
|
142
|
+
* whatever it was drawn as, because the submit path posts a flat
|
|
143
|
+
* `Record<string, string>`. A checkbox that returned a boolean would be the
|
|
144
|
+
* one field in the form with a different contract.
|
|
145
|
+
*/
|
|
146
|
+
export function buildField(field) {
|
|
147
|
+
const type = field.type ?? "text";
|
|
148
|
+
// Boolean/choice controls carry their own caption — a bare 16px box with
|
|
149
|
+
// the label somewhere above it reads as decoration, not as a question.
|
|
150
|
+
if (type === "checkbox") {
|
|
151
|
+
const row = el("label", `${PREFIX}-field-check`);
|
|
152
|
+
const box = el("input", `${PREFIX}-check`);
|
|
153
|
+
box.type = "checkbox";
|
|
154
|
+
box.checked = isTruthyValue(field.value);
|
|
155
|
+
if (field.required)
|
|
156
|
+
box.required = true;
|
|
157
|
+
row.appendChild(box);
|
|
158
|
+
const cap = el("span", `${PREFIX}-field-check-label`);
|
|
159
|
+
cap.textContent = field.label ?? field.placeholder ?? field.name;
|
|
160
|
+
row.appendChild(cap);
|
|
161
|
+
// Stringified so a field's answer is always a string, whatever it was
|
|
162
|
+
// drawn as — the submit path posts a flat Record<string,string>.
|
|
163
|
+
return {
|
|
164
|
+
node: row,
|
|
165
|
+
selfLabelled: true,
|
|
166
|
+
read: () => (box.checked ? "true" : "false"),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
if (type === "radio" && field.options?.length) {
|
|
170
|
+
const group = el("div", `${PREFIX}-field-group`);
|
|
171
|
+
// The `name` attribute is what makes radios mutually exclusive, so it has
|
|
172
|
+
// to be unique per rendered group — two proposal cards asking the same
|
|
173
|
+
// question would otherwise fight over one selection.
|
|
174
|
+
const groupName = `${PREFIX}-${field.name}-${Math.random().toString(36).slice(2, 9)}`;
|
|
175
|
+
for (const o of field.options) {
|
|
176
|
+
const row = el("label", `${PREFIX}-field-check`);
|
|
177
|
+
const radio = el("input", `${PREFIX}-check`);
|
|
178
|
+
radio.type = "radio";
|
|
179
|
+
radio.name = groupName;
|
|
180
|
+
radio.value = o;
|
|
181
|
+
// Required propagates to the inputs (one required radio makes the whole
|
|
182
|
+
// group required). It was dropped here, so a required choice reported
|
|
183
|
+
// itself as answered while nothing was selected.
|
|
184
|
+
if (field.required)
|
|
185
|
+
radio.required = true;
|
|
186
|
+
if (o === field.value)
|
|
187
|
+
radio.checked = true;
|
|
188
|
+
row.appendChild(radio);
|
|
189
|
+
const cap = el("span", `${PREFIX}-field-check-label`);
|
|
190
|
+
cap.textContent = o;
|
|
191
|
+
row.appendChild(cap);
|
|
192
|
+
group.appendChild(row);
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
node: group,
|
|
196
|
+
read: () => group.querySelector("input:checked")?.value ?? "",
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
let input;
|
|
200
|
+
if (type === "textarea") {
|
|
201
|
+
input = el("textarea", `${PREFIX}-field`);
|
|
202
|
+
}
|
|
203
|
+
else if (type === "select") {
|
|
204
|
+
const sel = el("select", `${PREFIX}-field`);
|
|
205
|
+
for (const o of field.options ?? []) {
|
|
206
|
+
const opt = document.createElement("option");
|
|
207
|
+
opt.value = o;
|
|
208
|
+
opt.textContent = o;
|
|
209
|
+
sel.appendChild(opt);
|
|
210
|
+
}
|
|
211
|
+
input = sel;
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
const i = el("input", `${PREFIX}-field`);
|
|
215
|
+
i.type = type === "number" ? "number" : type;
|
|
216
|
+
input = i;
|
|
217
|
+
}
|
|
218
|
+
if ("placeholder" in input && field.placeholder)
|
|
219
|
+
input.placeholder = field.label
|
|
220
|
+
? `${field.label} — ${field.placeholder}`
|
|
221
|
+
: field.placeholder;
|
|
222
|
+
else if ("placeholder" in input && field.label)
|
|
223
|
+
input.placeholder = field.label;
|
|
224
|
+
if (field.required)
|
|
225
|
+
input.required = true;
|
|
226
|
+
// Prefilled: the card hands back a value the user can accept or rewrite.
|
|
227
|
+
if (field.value !== undefined)
|
|
228
|
+
input.value = field.value;
|
|
229
|
+
return { node: input, read: () => input.value.trim() };
|
|
230
|
+
}
|
|
231
|
+
//# sourceMappingURL=specs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"specs.js","sourceRoot":"","sources":["../src/specs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every persisted value the widget owns, and the two rules that govern all of
|
|
3
|
+
* them.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from index.ts (#320). Storage was not one region of that file — it
|
|
6
|
+
* was 33 `localStorage` touches spread across 8 key-scoped concerns, from the
|
|
7
|
+
* draft textarea to the launcher position to the advanced-pane width. Each site
|
|
8
|
+
* re-implemented the same two rules, which is how they drifted:
|
|
9
|
+
*
|
|
10
|
+
* 1. AN EMPTY KEY MEANS PERSISTENCE IS OFF. Most keys are derived from
|
|
11
|
+
* `opts.persistKey`, which is optional: no persistKey, no writing to a
|
|
12
|
+
* visitor's browser. That was eight separate `if (!key) return` guards.
|
|
13
|
+
* 2. BLOCKED STORAGE IS NON-FATAL. Private windows, cleared site data and
|
|
14
|
+
* "block third-party cookies" all throw on access rather than returning
|
|
15
|
+
* null, so every read and every write needs a try/catch. That was ten
|
|
16
|
+
* of them, each with its own comment saying the same thing.
|
|
17
|
+
*
|
|
18
|
+
* THE ONE PLACE THOSE RULES ARE NOT UNIFORM is the point of collecting them.
|
|
19
|
+
* `hasOpenedBefore` returns TRUE when storage is unreadable, while every other
|
|
20
|
+
* flag defaults to false. That is deliberate — a returning reader should get
|
|
21
|
+
* the quiet pebble, not the first-visit pill on every single page load — but in
|
|
22
|
+
* index.ts it was one `return true` inside one of ten identical-looking catch
|
|
23
|
+
* blocks, indistinguishable from a typo. Here it is an argument with a name.
|
|
24
|
+
*
|
|
25
|
+
* These helpers take the key rather than closing over it, so they are callable
|
|
26
|
+
* from anywhere and testable without constructing a widget.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* A key that names a real slot.
|
|
30
|
+
*
|
|
31
|
+
* "Persistence is off" is spelled TWO ways in the caller — `null` for the five
|
|
32
|
+
* keys derived from `opts.persistKey`, `""` for the two derived from the
|
|
33
|
+
* advanced pane. Neither spelling is wrong and unifying them in index.ts would
|
|
34
|
+
* be a behaviour-free diff across a 5800-line file, so this is the one place
|
|
35
|
+
* that has to know both, and the only place that should.
|
|
36
|
+
*/
|
|
37
|
+
type StorageKey = string | null | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Read a string. Returns `fallback` when the key is off, absent, or storage is
|
|
40
|
+
* unreadable — the three cases callers have never needed to tell apart.
|
|
41
|
+
*/
|
|
42
|
+
export declare function readItem(key: StorageKey, fallback?: string): string;
|
|
43
|
+
/** Write a string, or REMOVE the key when given an empty value or null. */
|
|
44
|
+
export declare function writeItem(key: StorageKey, value: string | null): void;
|
|
45
|
+
/**
|
|
46
|
+
* Read a `"1"`/`"0"` flag.
|
|
47
|
+
*
|
|
48
|
+
* ABSENT AND UNREADABLE ARE DIFFERENT STATES, and collapsing them is a bug this
|
|
49
|
+
* function was written with and had to have removed. A key that is simply not
|
|
50
|
+
* set means "false" — the flag has never been turned on. `whenUnavailable` is
|
|
51
|
+
* only for the case where storage THREW, i.e. the answer is unknowable rather
|
|
52
|
+
* than no.
|
|
53
|
+
*
|
|
54
|
+
* The distinction is invisible in four of the five callers, whose answer is
|
|
55
|
+
* false either way. In the fifth it inverts the first-visit experience: a
|
|
56
|
+
* first-ever visitor has no `sg_ayca_opened` key, and answering "unavailable"
|
|
57
|
+
* there would greet them as a returning reader and suppress the pill they are
|
|
58
|
+
* supposed to see.
|
|
59
|
+
*/
|
|
60
|
+
export declare function readFlag(key: StorageKey, whenUnavailable?: boolean): boolean;
|
|
61
|
+
/** Write a `"1"`/`"0"` flag. */
|
|
62
|
+
export declare function writeFlag(key: StorageKey, on: boolean): void;
|
|
63
|
+
/**
|
|
64
|
+
* Read JSON and hand it to a validator before returning it.
|
|
65
|
+
*
|
|
66
|
+
* The validator is required rather than optional on purpose: everything in
|
|
67
|
+
* localStorage was written by a PREVIOUS BUILD of this widget and may not match
|
|
68
|
+
* the current shape. Two of the callers here already learned that — the tracked
|
|
69
|
+
* jobs list deliberately accepts entries with no `kind` because a build that
|
|
70
|
+
* predates the field wrote them, and dropping those would lose the card for
|
|
71
|
+
* work still running across a deploy.
|
|
72
|
+
*/
|
|
73
|
+
export declare function readJson<T>(key: StorageKey, isValid: (value: unknown) => value is T): T | null;
|
|
74
|
+
/** Write JSON, or REMOVE the key when given null. */
|
|
75
|
+
export declare function writeJson(key: StorageKey, value: unknown | null): void;
|
|
76
|
+
export {};
|
|
77
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH;;;;;;;;GAQG;AACH,KAAK,UAAU,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;AAG5C;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,UAAU,EAAE,QAAQ,SAAK,GAAG,MAAM,CAO/D;AAED,2EAA2E;AAC3E,wBAAgB,SAAS,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAQrE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,UAAU,EAAE,eAAe,UAAQ,GAAG,OAAO,CAO1E;AAED,gCAAgC;AAChC,wBAAgB,SAAS,CAAC,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,OAAO,GAAG,IAAI,CAE5D;AAED;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EACxB,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,IAAI,CAAC,GACtC,CAAC,GAAG,IAAI,CAUV;AAED,qDAAqD;AACrD,wBAAgB,SAAS,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,GAAG,IAAI,CAQtE"}
|
package/dist/storage.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every persisted value the widget owns, and the two rules that govern all of
|
|
3
|
+
* them.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from index.ts (#320). Storage was not one region of that file — it
|
|
6
|
+
* was 33 `localStorage` touches spread across 8 key-scoped concerns, from the
|
|
7
|
+
* draft textarea to the launcher position to the advanced-pane width. Each site
|
|
8
|
+
* re-implemented the same two rules, which is how they drifted:
|
|
9
|
+
*
|
|
10
|
+
* 1. AN EMPTY KEY MEANS PERSISTENCE IS OFF. Most keys are derived from
|
|
11
|
+
* `opts.persistKey`, which is optional: no persistKey, no writing to a
|
|
12
|
+
* visitor's browser. That was eight separate `if (!key) return` guards.
|
|
13
|
+
* 2. BLOCKED STORAGE IS NON-FATAL. Private windows, cleared site data and
|
|
14
|
+
* "block third-party cookies" all throw on access rather than returning
|
|
15
|
+
* null, so every read and every write needs a try/catch. That was ten
|
|
16
|
+
* of them, each with its own comment saying the same thing.
|
|
17
|
+
*
|
|
18
|
+
* THE ONE PLACE THOSE RULES ARE NOT UNIFORM is the point of collecting them.
|
|
19
|
+
* `hasOpenedBefore` returns TRUE when storage is unreadable, while every other
|
|
20
|
+
* flag defaults to false. That is deliberate — a returning reader should get
|
|
21
|
+
* the quiet pebble, not the first-visit pill on every single page load — but in
|
|
22
|
+
* index.ts it was one `return true` inside one of ten identical-looking catch
|
|
23
|
+
* blocks, indistinguishable from a typo. Here it is an argument with a name.
|
|
24
|
+
*
|
|
25
|
+
* These helpers take the key rather than closing over it, so they are callable
|
|
26
|
+
* from anywhere and testable without constructing a widget.
|
|
27
|
+
*/
|
|
28
|
+
const live = (key) => Boolean(key);
|
|
29
|
+
/**
|
|
30
|
+
* Read a string. Returns `fallback` when the key is off, absent, or storage is
|
|
31
|
+
* unreadable — the three cases callers have never needed to tell apart.
|
|
32
|
+
*/
|
|
33
|
+
export function readItem(key, fallback = "") {
|
|
34
|
+
if (!live(key))
|
|
35
|
+
return fallback;
|
|
36
|
+
try {
|
|
37
|
+
return localStorage.getItem(key) ?? fallback;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return fallback;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** Write a string, or REMOVE the key when given an empty value or null. */
|
|
44
|
+
export function writeItem(key, value) {
|
|
45
|
+
if (!live(key))
|
|
46
|
+
return;
|
|
47
|
+
try {
|
|
48
|
+
if (value)
|
|
49
|
+
localStorage.setItem(key, value);
|
|
50
|
+
else
|
|
51
|
+
localStorage.removeItem(key);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
/* storage full or blocked — the value just does not persist */
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Read a `"1"`/`"0"` flag.
|
|
59
|
+
*
|
|
60
|
+
* ABSENT AND UNREADABLE ARE DIFFERENT STATES, and collapsing them is a bug this
|
|
61
|
+
* function was written with and had to have removed. A key that is simply not
|
|
62
|
+
* set means "false" — the flag has never been turned on. `whenUnavailable` is
|
|
63
|
+
* only for the case where storage THREW, i.e. the answer is unknowable rather
|
|
64
|
+
* than no.
|
|
65
|
+
*
|
|
66
|
+
* The distinction is invisible in four of the five callers, whose answer is
|
|
67
|
+
* false either way. In the fifth it inverts the first-visit experience: a
|
|
68
|
+
* first-ever visitor has no `sg_ayca_opened` key, and answering "unavailable"
|
|
69
|
+
* there would greet them as a returning reader and suppress the pill they are
|
|
70
|
+
* supposed to see.
|
|
71
|
+
*/
|
|
72
|
+
export function readFlag(key, whenUnavailable = false) {
|
|
73
|
+
if (!live(key))
|
|
74
|
+
return false;
|
|
75
|
+
try {
|
|
76
|
+
return localStorage.getItem(key) === "1";
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return whenUnavailable;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** Write a `"1"`/`"0"` flag. */
|
|
83
|
+
export function writeFlag(key, on) {
|
|
84
|
+
writeItem(key, on ? "1" : "0");
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Read JSON and hand it to a validator before returning it.
|
|
88
|
+
*
|
|
89
|
+
* The validator is required rather than optional on purpose: everything in
|
|
90
|
+
* localStorage was written by a PREVIOUS BUILD of this widget and may not match
|
|
91
|
+
* the current shape. Two of the callers here already learned that — the tracked
|
|
92
|
+
* jobs list deliberately accepts entries with no `kind` because a build that
|
|
93
|
+
* predates the field wrote them, and dropping those would lose the card for
|
|
94
|
+
* work still running across a deploy.
|
|
95
|
+
*/
|
|
96
|
+
export function readJson(key, isValid) {
|
|
97
|
+
if (!live(key))
|
|
98
|
+
return null;
|
|
99
|
+
try {
|
|
100
|
+
const raw = localStorage.getItem(key);
|
|
101
|
+
if (!raw)
|
|
102
|
+
return null;
|
|
103
|
+
const parsed = JSON.parse(raw);
|
|
104
|
+
return isValid(parsed) ? parsed : null;
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return null; // corrupt, or unreadable — either way, start fresh
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** Write JSON, or REMOVE the key when given null. */
|
|
111
|
+
export function writeJson(key, value) {
|
|
112
|
+
if (!live(key))
|
|
113
|
+
return;
|
|
114
|
+
try {
|
|
115
|
+
if (value === null)
|
|
116
|
+
localStorage.removeItem(key);
|
|
117
|
+
else
|
|
118
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
/* storage full or blocked — non-fatal */
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAYH,MAAM,IAAI,GAAG,CAAC,GAAe,EAAiB,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;AAE9D;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,GAAe,EAAE,QAAQ,GAAG,EAAE;IACrD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,QAAQ,CAAC;IAChC,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,QAAQ,CAAC;IAClB,CAAC;AACH,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,SAAS,CAAC,GAAe,EAAE,KAAoB;IAC7D,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO;IACvB,IAAI,CAAC;QACH,IAAI,KAAK;YAAE,YAAY,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;;YACvC,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,+DAA+D;IACjE,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,QAAQ,CAAC,GAAe,EAAE,eAAe,GAAG,KAAK;IAC/D,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7B,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,eAAe,CAAC;IACzB,CAAC;AACH,CAAC;AAED,gCAAgC;AAChC,MAAM,UAAU,SAAS,CAAC,GAAe,EAAE,EAAW;IACpD,SAAS,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AACjC,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,QAAQ,CACtB,GAAe,EACf,OAAuC;IAEvC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACtC,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QACtB,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACxC,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,CAAC,mDAAmD;IAClE,CAAC;AACH,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,SAAS,CAAC,GAAe,EAAE,KAAqB;IAC9D,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO;IACvB,IAAI,CAAC;QACH,IAAI,KAAK,KAAK,IAAI;YAAE,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;;YAC5C,YAAY,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC;QACP,yCAAyC;IAC3C,CAAC;AACH,CAAC"}
|
package/dist/styles.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Does the page around us define the PLATFORM's control variables?
|
|
3
|
+
*
|
|
4
|
+
* When the widget is mounted inside one of our own apps, `--input` / `--ring` /
|
|
5
|
+
* `--card` / … are already on `<html>` (set by the host's own stylesheet), and
|
|
6
|
+
* a text field in chat should be the same object as a text field on the page
|
|
7
|
+
* behind it — same border, same focus ring, same radius, flipping together when
|
|
8
|
+
* the app toggles `.dark`. When the widget is embedded on a customer's site,
|
|
9
|
+
* none of that exists and it must fall back to its own `--aiw-*` palette.
|
|
10
|
+
*
|
|
11
|
+
* The check is deliberately shape-based, not merely presence-based: `--input`
|
|
12
|
+
* is a plausible name for anyone to invent, but our tokens are HSL COMPONENTS
|
|
13
|
+
* ("220 9% 85%") so they can be used with an alpha. A site whose `--input` is
|
|
14
|
+
* `#fff` fails the test and we leave its page alone — the failure mode of
|
|
15
|
+
* guessing wrong here is an unreadable chat form on someone else's website.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* The near-black the user bubble mixes the accent into.
|
|
19
|
+
*
|
|
20
|
+
* Exported because `index.ts` derives that bubble's foreground against the
|
|
21
|
+
* RESULT of the mix, not against the accent (#307 one level down: over the teal
|
|
22
|
+
* all three hosts pass, the bubble is `#4a9d9e` and the old literal `#fff` was
|
|
23
|
+
* 3.18:1). Two copies of this number would put the derivation and the paint on
|
|
24
|
+
* different backgrounds, which is the same bug again.
|
|
25
|
+
*/
|
|
26
|
+
export declare const USER_BUBBLE_INK = "#04191b";
|
|
27
|
+
export declare function hostDefinesPlatformTokens(): boolean;
|
|
28
|
+
export declare function injectStyles(side: "left" | "right"): void;
|
|
29
|
+
//# sourceMappingURL=styles.d.ts.map
|