@dvina/widget-kit 0.0.0-stage → 0.0.34
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/dist/compile.d.ts +2 -0
- package/dist/compile.js +18 -0
- package/dist/compose.d.ts +13 -0
- package/dist/compose.js +103 -0
- package/dist/db.d.ts +36 -0
- package/dist/db.js +156 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +8 -0
- package/dist/kit-react.js +1 -0
- package/dist/kit-tailwind.css +141 -0
- package/dist/kit.css +1 -0
- package/dist/kit.js +1 -0
- package/dist/react-loader.js +2 -0
- package/dist/standalone.js +1 -0
- package/dist/tailwind.js +952 -0
- package/dist/tokens.css +1 -0
- package/package.json +64 -3
- package/README.md +0 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dvina
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/compile.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// src/runtime/compile.ts
|
|
2
|
+
async function compileReactWidget(source) {
|
|
3
|
+
try {
|
|
4
|
+
const { transform } = await import("sucrase");
|
|
5
|
+
const { code } = transform(source, {
|
|
6
|
+
transforms: ["jsx", "typescript"],
|
|
7
|
+
jsxRuntime: "automatic",
|
|
8
|
+
production: true,
|
|
9
|
+
filePath: "widget.tsx"
|
|
10
|
+
});
|
|
11
|
+
return { code };
|
|
12
|
+
} catch (error) {
|
|
13
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export {
|
|
17
|
+
compileReactWidget
|
|
18
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type WidgetKind = "html" | "react";
|
|
2
|
+
/** The meta a composed page declares itself with: `<meta name="dvina-widget" content="html|react">`. */
|
|
3
|
+
export declare const WIDGET_PAGE_META: string;
|
|
4
|
+
/** A saved widget as a page of its own: the kit's tokens, stylesheet and script inlined with its code. */
|
|
5
|
+
export declare function composeWidgetPage(input: { kind: WidgetKind; title: string; code: string }): Promise<string>;
|
|
6
|
+
/** Whether a file is a composed widget page, and which kind; null for anything else. */
|
|
7
|
+
export declare function widgetPageKind(page: string): WidgetKind | null;
|
|
8
|
+
/** The widget's own code from a composed page; the text itself for anything else. */
|
|
9
|
+
export declare function widgetSource(page: string): string;
|
|
10
|
+
/** The data a downloaded widget opens with: the reader's own, as it was when they downloaded it. */
|
|
11
|
+
export type WidgetSeedData = { artifactId?: string; version: number; collections: Record<string, Record<string, unknown>> };
|
|
12
|
+
/** A composed page with its seed, for a download; a fresh file id unless one is given. Throws for any other page. */
|
|
13
|
+
export declare function seedWidgetPage(page: string, data: WidgetSeedData, fileId?: string): string;
|
package/dist/compose.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// src/runtime/compose.ts
|
|
2
|
+
import {
|
|
3
|
+
kitCss,
|
|
4
|
+
kitReactScript,
|
|
5
|
+
kitScript,
|
|
6
|
+
reactLoaderScript,
|
|
7
|
+
standaloneScript,
|
|
8
|
+
tailwindCss,
|
|
9
|
+
tailwindScript,
|
|
10
|
+
tokensCss
|
|
11
|
+
} from "./index.js";
|
|
12
|
+
|
|
13
|
+
// src/runtime/compile.ts
|
|
14
|
+
async function compileReactWidget(source) {
|
|
15
|
+
try {
|
|
16
|
+
const { transform } = await import("sucrase");
|
|
17
|
+
const { code } = transform(source, {
|
|
18
|
+
transforms: ["jsx", "typescript"],
|
|
19
|
+
jsxRuntime: "automatic",
|
|
20
|
+
production: true,
|
|
21
|
+
filePath: "widget.tsx"
|
|
22
|
+
});
|
|
23
|
+
return { code };
|
|
24
|
+
} catch (error) {
|
|
25
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/runtime/compose.ts
|
|
30
|
+
var WIDGET_PAGE_META = "dvina-widget";
|
|
31
|
+
var START = "<!-- dvina:widget -->";
|
|
32
|
+
var END = "<!-- /dvina:widget -->";
|
|
33
|
+
var escapeHtml = (text) => text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
34
|
+
var inScript = (text) => text.replace(/<\/(script|style)/gi, "<\\/$1").replace(/<!--/g, "<\\!--");
|
|
35
|
+
var literal = (value) => JSON.stringify(value).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
36
|
+
var STANDALONE_ID = "dvina-standalone";
|
|
37
|
+
var standalone = () => `<script id="${STANDALONE_ID}">${inScript(standaloneScript)}</script>`;
|
|
38
|
+
function head(kind, title, extra = "") {
|
|
39
|
+
return [
|
|
40
|
+
'<meta charset="utf-8" />',
|
|
41
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
42
|
+
`<meta name="${WIDGET_PAGE_META}" content="${kind}" />`,
|
|
43
|
+
`<title>${escapeHtml(title)}</title>`,
|
|
44
|
+
`<style id="dvina-tokens">${inScript(tokensCss)}</style>`,
|
|
45
|
+
`<style id="dvina-kit">${inScript(kitCss)}</style>`,
|
|
46
|
+
`<script id="dvina-kit-script">${inScript(kitScript)}</script>`,
|
|
47
|
+
standalone(),
|
|
48
|
+
extra
|
|
49
|
+
].join("");
|
|
50
|
+
}
|
|
51
|
+
async function composeWidgetPage(input) {
|
|
52
|
+
const title = input.title.trim() || "Widget";
|
|
53
|
+
if (input.kind === "html") {
|
|
54
|
+
return `<!doctype html><html lang="en"><head>${head("html", title)}</head><body>${START}
|
|
55
|
+
${input.code}
|
|
56
|
+
${END}</body></html>`;
|
|
57
|
+
}
|
|
58
|
+
const compiled = await compileReactWidget(input.code);
|
|
59
|
+
if ("error" in compiled) throw new Error(`The React widget does not compile: ${compiled.error}`);
|
|
60
|
+
const react = [
|
|
61
|
+
`<style type="text/tailwindcss" id="dvina-tailwind">${inScript(tailwindCss)}</style>`,
|
|
62
|
+
`<script id="dvina-tailwind-script">${inScript(tailwindScript)}</script>`,
|
|
63
|
+
`<script id="dvina-react-loader">${inScript(reactLoaderScript)}</script>`
|
|
64
|
+
].join("");
|
|
65
|
+
const mount = `var root=document.getElementById("root");window.__dvinaMountReact(root,${literal(compiled.code)},{text:${literal(kitReactScript)}}).catch(function(error){root.textContent=String(error&&error.message||error);});`;
|
|
66
|
+
return `<!doctype html><html lang="en"><head>${head("react", title, react)}</head><body><div id="root"></div><script type="text/plain" id="dvina-widget-source">${START}
|
|
67
|
+
${inScript(input.code)}
|
|
68
|
+
${END}</script><script>${mount}</script></body></html>`;
|
|
69
|
+
}
|
|
70
|
+
function widgetPageKind(page) {
|
|
71
|
+
const match = new RegExp(`<meta name="${WIDGET_PAGE_META}" content="(html|react)"`).exec(page.slice(0, 4096));
|
|
72
|
+
return match ? match[1] : null;
|
|
73
|
+
}
|
|
74
|
+
function widgetSource(page) {
|
|
75
|
+
const start = page.indexOf(START);
|
|
76
|
+
const end = page.lastIndexOf(END);
|
|
77
|
+
if (widgetPageKind(page) === null || start < 0 || end < start) return page;
|
|
78
|
+
const code = page.slice(start + START.length, end).replace(/^\n/, "").replace(/\n$/, "");
|
|
79
|
+
return widgetPageKind(page) === "react" ? code.replace(/<\\\/(script|style)/gi, "</$1").replace(/<\\!--/g, "<!--") : code;
|
|
80
|
+
}
|
|
81
|
+
var SEED = /<script type="application\/json" id="dvina-seed">[\s\S]*?<\/script>/;
|
|
82
|
+
function seedWidgetPage(page, data, fileId = crypto.randomUUID()) {
|
|
83
|
+
if (widgetPageKind(page) === null) throw new Error("Only a composed widget page takes a seed.");
|
|
84
|
+
const seed = {
|
|
85
|
+
fileId,
|
|
86
|
+
...data.artifactId ? { artifactId: data.artifactId } : {},
|
|
87
|
+
version: data.version,
|
|
88
|
+
collections: data.collections
|
|
89
|
+
};
|
|
90
|
+
const element = `<script type="application/json" id="dvina-seed">${literal(seed)}</script>`;
|
|
91
|
+
const end = page.search(/<\/head>/i);
|
|
92
|
+
if (end < 0) throw new Error("The page has no head to seed.");
|
|
93
|
+
const head2 = page.slice(0, end).replace(SEED, "");
|
|
94
|
+
const addition = head2.includes(`id="${STANDALONE_ID}"`) ? element : element + standalone();
|
|
95
|
+
return head2 + addition + page.slice(end);
|
|
96
|
+
}
|
|
97
|
+
export {
|
|
98
|
+
WIDGET_PAGE_META,
|
|
99
|
+
composeWidgetPage,
|
|
100
|
+
seedWidgetPage,
|
|
101
|
+
widgetPageKind,
|
|
102
|
+
widgetSource
|
|
103
|
+
};
|
package/dist/db.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };
|
|
2
|
+
export type Doc = { [key: string]: JsonValue };
|
|
3
|
+
export type Collections = Record<string, Record<string, Doc>>;
|
|
4
|
+
export type Snapshot = { version: number; collections: Collections };
|
|
5
|
+
export type Op = { op: "set" | "update" | "delete"; collection: string; id: string; doc?: Doc };
|
|
6
|
+
export type Changed = { collection: string; id: string; doc: Doc | null };
|
|
7
|
+
/** How long writes gather before they go to the transport as one batch. */
|
|
8
|
+
export declare const WRITE_BATCH_MS: number;
|
|
9
|
+
export interface DbTransport {
|
|
10
|
+
read(): Promise<Snapshot>;
|
|
11
|
+
apply(ops: Op[]): Promise<{ version: number; changed: Changed[] }>;
|
|
12
|
+
}
|
|
13
|
+
export type ListOptions = {
|
|
14
|
+
where?: Doc | ((doc: Doc & { id: string }) => boolean);
|
|
15
|
+
orderBy?: string | [string, "asc" | "desc"];
|
|
16
|
+
limit?: number;
|
|
17
|
+
};
|
|
18
|
+
export interface Collection {
|
|
19
|
+
get(id: string): Promise<(Doc & { id: string }) | null>;
|
|
20
|
+
list(options?: ListOptions): Promise<Array<Doc & { id: string }>>;
|
|
21
|
+
set(id: string, doc: Doc): Promise<void>;
|
|
22
|
+
update(id: string, patch: Doc): Promise<void>;
|
|
23
|
+
delete(id: string): Promise<void>;
|
|
24
|
+
subscribe(listener: () => void): () => void;
|
|
25
|
+
}
|
|
26
|
+
export interface Db {
|
|
27
|
+
collection(name: string): Collection;
|
|
28
|
+
}
|
|
29
|
+
export interface KeyValueStorage {
|
|
30
|
+
get(key: string): Promise<JsonValue | undefined>;
|
|
31
|
+
set(key: string, value: JsonValue): Promise<void>;
|
|
32
|
+
delete(key: string): Promise<void>;
|
|
33
|
+
list(options?: { prefix?: string }): Promise<string[]>;
|
|
34
|
+
}
|
|
35
|
+
/** `dvina.use('db')` and `dvina.use('storage')` over one transport. */
|
|
36
|
+
export declare function createDb(transport: DbTransport, onError?: (error: Error) => void): { db: Db; storage: KeyValueStorage };
|
package/dist/db.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// src/runtime/db.ts
|
|
2
|
+
var WRITE_BATCH_MS = 300;
|
|
3
|
+
var NAME = /^[A-Za-z0-9_.:-]{1,128}$/;
|
|
4
|
+
function checkName(kind, value) {
|
|
5
|
+
if (typeof value !== "string" || !NAME.test(value)) {
|
|
6
|
+
throw new Error(`A ${kind} is 1\u2013128 letters, digits or . _ : - ("${String(value)}" is not).`);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
function compare(a, b) {
|
|
10
|
+
if (a === b) return 0;
|
|
11
|
+
if (a === void 0 || a === null) return 1;
|
|
12
|
+
if (b === void 0 || b === null) return -1;
|
|
13
|
+
return a < b ? -1 : 1;
|
|
14
|
+
}
|
|
15
|
+
function createStore(transport, onError) {
|
|
16
|
+
let snapshot = null;
|
|
17
|
+
let loading = null;
|
|
18
|
+
let queue = [];
|
|
19
|
+
let timer = 0;
|
|
20
|
+
let inFlight = false;
|
|
21
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
22
|
+
const notify = () => listeners.forEach((listener) => listener());
|
|
23
|
+
function load(force = false) {
|
|
24
|
+
if (snapshot && !force) return Promise.resolve(snapshot);
|
|
25
|
+
if (loading && !force) return loading;
|
|
26
|
+
loading = transport.read().then(
|
|
27
|
+
(read) => {
|
|
28
|
+
loading = null;
|
|
29
|
+
snapshot = { version: read.version, collections: structuredClone(read.collections) };
|
|
30
|
+
for (const { op } of queue) applyLocally(op);
|
|
31
|
+
notify();
|
|
32
|
+
return snapshot;
|
|
33
|
+
},
|
|
34
|
+
(error) => {
|
|
35
|
+
loading = null;
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
return loading;
|
|
40
|
+
}
|
|
41
|
+
function applyLocally(op) {
|
|
42
|
+
if (!snapshot) return;
|
|
43
|
+
const docs = snapshot.collections[op.collection] ??= {};
|
|
44
|
+
if (op.op === "delete") delete docs[op.id];
|
|
45
|
+
else if (op.op === "set") docs[op.id] = structuredClone(op.doc ?? {});
|
|
46
|
+
else docs[op.id] = { ...docs[op.id] ?? {}, ...structuredClone(op.doc ?? {}) };
|
|
47
|
+
}
|
|
48
|
+
async function flush() {
|
|
49
|
+
timer = 0;
|
|
50
|
+
if (inFlight || queue.length === 0) return;
|
|
51
|
+
inFlight = true;
|
|
52
|
+
const batch = queue;
|
|
53
|
+
queue = [];
|
|
54
|
+
try {
|
|
55
|
+
const result = await transport.apply(batch.map(({ op }) => op));
|
|
56
|
+
if (snapshot) {
|
|
57
|
+
snapshot.version = result.version;
|
|
58
|
+
for (const { collection, id, doc } of result.changed) {
|
|
59
|
+
const docs = snapshot.collections[collection] ??= {};
|
|
60
|
+
if (doc === null) delete docs[id];
|
|
61
|
+
else docs[id] = doc;
|
|
62
|
+
}
|
|
63
|
+
for (const { op } of queue) applyLocally(op);
|
|
64
|
+
}
|
|
65
|
+
batch.forEach(({ settle }) => settle());
|
|
66
|
+
notify();
|
|
67
|
+
} catch (error) {
|
|
68
|
+
const reason = error instanceof Error ? error : new Error(String(error));
|
|
69
|
+
batch.forEach(({ settle }) => settle(reason));
|
|
70
|
+
onError(reason);
|
|
71
|
+
await load(true).catch(() => void 0);
|
|
72
|
+
} finally {
|
|
73
|
+
inFlight = false;
|
|
74
|
+
if (queue.length) schedule();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function schedule() {
|
|
78
|
+
if (!timer) timer = window.setTimeout(() => void flush(), WRITE_BATCH_MS);
|
|
79
|
+
}
|
|
80
|
+
async function write(op) {
|
|
81
|
+
checkName("collection", op.collection);
|
|
82
|
+
checkName("document id", op.id);
|
|
83
|
+
await load();
|
|
84
|
+
return new Promise((resolve, reject) => {
|
|
85
|
+
applyLocally(op);
|
|
86
|
+
queue.push({ op, settle: (error) => error ? reject(error) : resolve() });
|
|
87
|
+
notify();
|
|
88
|
+
schedule();
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
load,
|
|
93
|
+
/** Reads the host's data again (the frame came back into view), unless writes are on their way. */
|
|
94
|
+
refresh() {
|
|
95
|
+
if (snapshot && !inFlight && queue.length === 0) void load(true).catch(() => void 0);
|
|
96
|
+
},
|
|
97
|
+
collection(name) {
|
|
98
|
+
checkName("collection", name);
|
|
99
|
+
const docs = () => snapshot?.collections[name] ?? {};
|
|
100
|
+
return Object.freeze({
|
|
101
|
+
async get(id) {
|
|
102
|
+
await load();
|
|
103
|
+
const doc = docs()[id];
|
|
104
|
+
return doc ? { ...structuredClone(doc), id } : null;
|
|
105
|
+
},
|
|
106
|
+
async list(options = {}) {
|
|
107
|
+
await load();
|
|
108
|
+
let rows = Object.entries(docs()).map(([id, doc]) => ({ ...structuredClone(doc), id }));
|
|
109
|
+
const { where, orderBy, limit } = options;
|
|
110
|
+
if (typeof where === "function") rows = rows.filter(where);
|
|
111
|
+
else if (where) rows = rows.filter((row) => Object.entries(where).every(([key, value]) => row[key] === value));
|
|
112
|
+
if (orderBy) {
|
|
113
|
+
const [field, direction] = Array.isArray(orderBy) ? orderBy : [orderBy, "asc"];
|
|
114
|
+
rows.sort((a, b) => compare(a[field], b[field]) * (direction === "desc" ? -1 : 1));
|
|
115
|
+
}
|
|
116
|
+
return typeof limit === "number" ? rows.slice(0, Math.max(0, limit)) : rows;
|
|
117
|
+
},
|
|
118
|
+
set: (id, doc) => write({ op: "set", collection: name, id, doc }),
|
|
119
|
+
update: (id, patch) => write({ op: "update", collection: name, id, doc: patch }),
|
|
120
|
+
delete: (id) => write({ op: "delete", collection: name, id }),
|
|
121
|
+
/** Calls back whenever the collection may have changed (a write here, or fresh data from the host). */
|
|
122
|
+
subscribe(listener) {
|
|
123
|
+
listeners.add(listener);
|
|
124
|
+
return () => listeners.delete(listener);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
var KV_COLLECTION = "_kv";
|
|
131
|
+
function createDb(transport, onError = () => void 0) {
|
|
132
|
+
const store = createStore(transport, onError);
|
|
133
|
+
const refresh = () => store.refresh();
|
|
134
|
+
window.addEventListener("focus", refresh);
|
|
135
|
+
document.addEventListener("visibilitychange", () => document.visibilityState === "visible" && refresh());
|
|
136
|
+
const db = Object.freeze({
|
|
137
|
+
collection: (name) => store.collection(name)
|
|
138
|
+
});
|
|
139
|
+
const kv = () => store.collection(KV_COLLECTION);
|
|
140
|
+
const storage = Object.freeze({
|
|
141
|
+
async get(key) {
|
|
142
|
+
return (await kv().get(key))?.value;
|
|
143
|
+
},
|
|
144
|
+
set: (key, value) => kv().set(key, { value }),
|
|
145
|
+
delete: (key) => kv().delete(key),
|
|
146
|
+
async list(options = {}) {
|
|
147
|
+
const rows = await kv().list();
|
|
148
|
+
return rows.map((row) => row.id).filter((key) => !options.prefix || key.startsWith(options.prefix));
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
return { db, storage };
|
|
152
|
+
}
|
|
153
|
+
export {
|
|
154
|
+
WRITE_BATCH_MS,
|
|
155
|
+
createDb
|
|
156
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** The kit's tokens (`--dv-*`), light and dark, in their layer: inlined first wherever the kit is used. */
|
|
2
|
+
export declare const tokensCss: string;
|
|
3
|
+
/** The kit's stylesheet: fonts, bare elements, classes and the SVG kit; no tokens (tokensCss). */
|
|
4
|
+
export declare const kitCss: string;
|
|
5
|
+
/** The kit's script, an IIFE: icons, slider fills, SVG arrow heads, `window.Dvina` (chart themes, formatters). */
|
|
6
|
+
export declare const kitScript: string;
|
|
7
|
+
/** The React components as an IIFE that reads `window.React` and sets `window.DvinaKit`. */
|
|
8
|
+
export declare const kitReactScript: string;
|
|
9
|
+
/** React mode's loader, an IIFE: React from its pinned CDN, the kit's components, then the widget module mounted. */
|
|
10
|
+
export declare const reactLoaderScript: string;
|
|
11
|
+
/** A downloaded widget's data on its own, an IIFE: `window.dvina` over its seed and localStorage, when no viewer set one. */
|
|
12
|
+
export declare const standaloneScript: string;
|
|
13
|
+
/** Tailwind's theme over the kit's tokens, as text for a text/tailwindcss style; React artifacts only. */
|
|
14
|
+
export declare const tailwindCss: string;
|
|
15
|
+
/** Tailwind v4's browser build (pinned), which compiles that theme and the classes it finds in the page. */
|
|
16
|
+
export declare const tailwindScript: string;
|