@observablehq/notebook-kit 1.5.2 → 1.6.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/dist/package.json +12 -12
- package/dist/src/databases/bigquery.d.ts +2 -1
- package/dist/src/databases/bigquery.js +6 -2
- package/dist/src/databases/snowflake.js +1 -1
- package/dist/src/javascript/imports/jsr.d.ts +1 -1
- package/dist/src/javascript/imports/jsr.js +1 -1
- package/dist/src/javascript/imports.d.ts +2 -2
- package/dist/src/javascript/imports.js +2 -2
- package/dist/src/lib/notebook.d.ts +6 -2
- package/dist/src/lib/serialize.js +12 -2
- package/dist/src/runtime/index.d.ts +2 -0
- package/dist/src/runtime/stdlib/dom/canvas.d.ts +1 -0
- package/dist/src/runtime/stdlib/dom/canvas.js +1 -0
- package/dist/src/runtime/stdlib/dom/download.d.ts +2 -0
- package/dist/src/runtime/stdlib/dom/download.js +32 -0
- package/dist/src/runtime/stdlib/dom/element.d.ts +2 -0
- package/dist/src/runtime/stdlib/dom/element.js +34 -0
- package/dist/src/runtime/stdlib/dom/index.d.ts +5 -0
- package/dist/src/runtime/stdlib/dom/index.js +5 -0
- package/dist/src/runtime/stdlib/dom/input.d.ts +1 -0
- package/dist/src/runtime/stdlib/dom/input.js +6 -0
- package/dist/src/runtime/stdlib/dom/range.d.ts +2 -0
- package/dist/src/runtime/stdlib/dom/range.js +13 -0
- package/dist/src/runtime/stdlib/dom/select.d.ts +2 -0
- package/dist/src/runtime/stdlib/dom/select.js +10 -0
- package/dist/src/runtime/stdlib/dom/svg.d.ts +1 -0
- package/dist/src/runtime/stdlib/dom/svg.js +1 -0
- package/dist/src/runtime/stdlib/dom/text.d.ts +1 -0
- package/dist/src/runtime/stdlib/dom/text.js +1 -0
- package/dist/src/runtime/stdlib/fileAttachment.d.ts +1 -0
- package/dist/src/runtime/stdlib/fileAttachment.js +11 -3
- package/dist/src/runtime/stdlib/generators/dark.d.ts +1 -0
- package/dist/src/runtime/stdlib/generators/dark.js +25 -0
- package/dist/src/runtime/stdlib/generators/index.d.ts +1 -0
- package/dist/src/runtime/stdlib/generators/index.js +1 -0
- package/dist/src/runtime/stdlib/index.d.ts +3 -0
- package/dist/src/runtime/stdlib/index.js +3 -0
- package/dist/src/runtime/stdlib/md.js +7 -9
- package/dist/src/runtime/stdlib/promises/delay.d.ts +4 -0
- package/dist/src/runtime/stdlib/promises/delay.js +3 -0
- package/dist/src/runtime/stdlib/promises/index.d.ts +3 -0
- package/dist/src/runtime/stdlib/promises/index.js +3 -0
- package/dist/src/runtime/stdlib/promises/tick.d.ts +2 -0
- package/dist/src/runtime/stdlib/promises/tick.js +5 -0
- package/dist/src/runtime/stdlib/promises/when.d.ts +2 -0
- package/dist/src/runtime/stdlib/promises/when.js +18 -0
- package/dist/src/runtime/stdlib/require.js +10 -2
- package/dist/src/styles/global.css +3 -7
- package/dist/src/templates/default.html +6 -18
- package/dist/src/vite/observable.js +19 -5
- package/package.json +12 -12
package/dist/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/observablehq/notebook-kit.git"
|
|
7
7
|
},
|
|
8
|
-
"version": "1.
|
|
8
|
+
"version": "1.6.0",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"scripts": {
|
|
11
11
|
"test": "vitest",
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
"./*.css": "./dist/src/styles/*.css"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@fontsource/inter": "^5.2.
|
|
46
|
-
"@fontsource/source-serif-4": "^5.2.
|
|
47
|
-
"@fontsource/spline-sans-mono": "^5.2.
|
|
45
|
+
"@fontsource-variable/inter": "^5.2.8",
|
|
46
|
+
"@fontsource-variable/source-serif-4": "^5.2.9",
|
|
47
|
+
"@fontsource-variable/spline-sans-mono": "^5.2.8",
|
|
48
48
|
"@lezer/common": "^1.2.3",
|
|
49
49
|
"@lezer/css": "^1.2.1",
|
|
50
50
|
"@lezer/highlight": "^1.2.1",
|
|
@@ -65,28 +65,28 @@
|
|
|
65
65
|
"vite": "^7.0.0"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
-
"@databricks/sql": "^1.
|
|
68
|
+
"@databricks/sql": "^1.14.0",
|
|
69
69
|
"@duckdb/node-api": "^1.3.2-alpha.26",
|
|
70
70
|
"@eslint/js": "^9.29.0",
|
|
71
|
-
"@google-cloud/bigquery": "^8.
|
|
71
|
+
"@google-cloud/bigquery": "^8.3.0",
|
|
72
72
|
"@types/jsdom": "^21.1.7",
|
|
73
73
|
"@types/markdown-it": "^14.1.2",
|
|
74
74
|
"bun-types": "^1.2.20",
|
|
75
75
|
"eslint": "^9.29.0",
|
|
76
76
|
"globals": "^16.2.0",
|
|
77
77
|
"htl": "^0.3.1",
|
|
78
|
-
"postgres": "^3.4.
|
|
79
|
-
"snowflake-sdk": "^2.
|
|
78
|
+
"postgres": "^3.4.9",
|
|
79
|
+
"snowflake-sdk": "^2.4.0",
|
|
80
80
|
"tsx": "^4.20.3",
|
|
81
81
|
"typescript-eslint": "^8.35.0",
|
|
82
82
|
"vitest": "^3.2.4"
|
|
83
83
|
},
|
|
84
84
|
"peerDependencies": {
|
|
85
|
-
"@databricks/sql": "^1.
|
|
85
|
+
"@databricks/sql": "^1.14.0",
|
|
86
86
|
"@duckdb/node-api": "^1.3.2-alpha.26",
|
|
87
|
-
"@google-cloud/bigquery": "^8.
|
|
88
|
-
"postgres": "^3.4.
|
|
89
|
-
"snowflake-sdk": "^2.
|
|
87
|
+
"@google-cloud/bigquery": "^8.3.0",
|
|
88
|
+
"postgres": "^3.4.9",
|
|
89
|
+
"snowflake-sdk": "^2.4.0"
|
|
90
90
|
},
|
|
91
91
|
"peerDependenciesMeta": {
|
|
92
92
|
"@databricks/sql": {
|
|
@@ -5,8 +5,9 @@ export type BigQueryConfig = {
|
|
|
5
5
|
keyFilename?: string;
|
|
6
6
|
keyFile?: string;
|
|
7
7
|
projectId?: string;
|
|
8
|
+
dataset?: string;
|
|
8
9
|
};
|
|
9
|
-
export default function bigquery({ type, ...options }: BigQueryConfig): QueryTemplateFunction;
|
|
10
|
+
export default function bigquery({ type, dataset, ...options }: BigQueryConfig): QueryTemplateFunction;
|
|
10
11
|
export declare function replacer(this: {
|
|
11
12
|
[key: string]: unknown;
|
|
12
13
|
}, key: string, value: unknown): unknown;
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { BigQuery } from "@google-cloud/bigquery";
|
|
2
2
|
import { BigQueryDate, BigQueryDatetime, BigQueryTimestamp } from "@google-cloud/bigquery";
|
|
3
3
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
4
|
-
export default function bigquery({ type, ...options }) {
|
|
4
|
+
export default function bigquery({ type, dataset, ...options }) {
|
|
5
5
|
return async (strings, ...params) => {
|
|
6
6
|
const bigquery = new BigQuery(options);
|
|
7
7
|
const date = new Date();
|
|
8
|
-
const [job] = await bigquery.createQueryJob({
|
|
8
|
+
const [job] = await bigquery.createQueryJob({
|
|
9
|
+
query: strings.join("?"),
|
|
10
|
+
params,
|
|
11
|
+
...(dataset && { defaultDataset: { datasetId: dataset } }),
|
|
12
|
+
});
|
|
9
13
|
const [rows, , response] = await job.getQueryResults();
|
|
10
14
|
return { rows, schema: getTableSchema(response.schema), duration: Date.now() - +date, date };
|
|
11
15
|
};
|
|
@@ -62,7 +62,7 @@ async function execute(connection, sql, params) {
|
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
64
|
function getStatementSchema(statement) {
|
|
65
|
-
return statement.getColumns()
|
|
65
|
+
return statement.getColumns()?.map(getColumnSchema) ?? [];
|
|
66
66
|
}
|
|
67
67
|
function getColumnSchema(column) {
|
|
68
68
|
return { name: column.getName(), type: getColumnType(column), nullable: column.isNullable() };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/** If specifier is
|
|
1
|
+
/** If specifier is a jsr: protocol import, resolves it. */
|
|
2
2
|
export declare function resolveJsrImport(specifier: string): string;
|
|
@@ -8,9 +8,9 @@ export declare function checkExports(body: Node, { input }: {
|
|
|
8
8
|
}): void;
|
|
9
9
|
/** Returns true if the body includes an import declaration. */
|
|
10
10
|
export declare function hasImportDeclaration(body: Node): boolean;
|
|
11
|
-
/** Returns true if the given node is
|
|
11
|
+
/** Returns true if the given node is an import.meta.resolve(…) call. */
|
|
12
12
|
export declare function isImportMetaResolve(node: CallExpression): boolean;
|
|
13
|
-
/** Returns true if the given node is
|
|
13
|
+
/** Returns true if the given node is an import.meta.url expression. */
|
|
14
14
|
export declare function isImportMetaUrl(node: MemberExpression): boolean;
|
|
15
15
|
export type RewriteImportOptions = {
|
|
16
16
|
/** If true, resolve local imports relative to document.baseURI. */
|
|
@@ -25,7 +25,7 @@ export function hasImportDeclaration(body) {
|
|
|
25
25
|
});
|
|
26
26
|
return has;
|
|
27
27
|
}
|
|
28
|
-
/** Returns true if the given node is
|
|
28
|
+
/** Returns true if the given node is an import.meta.resolve(…) call. */
|
|
29
29
|
export function isImportMetaResolve(node) {
|
|
30
30
|
return (node.callee.type === "MemberExpression" &&
|
|
31
31
|
node.callee.object.type === "MetaProperty" &&
|
|
@@ -35,7 +35,7 @@ export function isImportMetaResolve(node) {
|
|
|
35
35
|
node.callee.property.name === "resolve" &&
|
|
36
36
|
node.arguments.length > 0);
|
|
37
37
|
}
|
|
38
|
-
/** Returns true if the given node is
|
|
38
|
+
/** Returns true if the given node is an import.meta.url expression. */
|
|
39
39
|
export function isImportMetaUrl(node) {
|
|
40
40
|
return (node.object.type === "MetaProperty" &&
|
|
41
41
|
node.object.meta.name === "import" &&
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
export type NotebookTheme = "air" | "coffee" | "cotton" | "deep-space" | "glacier" | "ink" | "midnight" | "near-midnight" | "ocean-floor" | "parchment" | "slate" | "stark" | "sun-faded";
|
|
2
|
+
export type LightDarkNotebookTheme = {
|
|
3
|
+
light: NotebookTheme;
|
|
4
|
+
dark: NotebookTheme;
|
|
5
|
+
};
|
|
2
6
|
export interface NotebookSpec {
|
|
3
7
|
/** the notebook’s cells, in top-to-bottom document order */
|
|
4
8
|
cells?: CellSpec[];
|
|
5
9
|
/** the notebook title, if any; extracted from the first h1 */
|
|
6
10
|
title?: string;
|
|
7
|
-
/** the notebook theme; defaults to "air" */
|
|
8
|
-
theme?: NotebookTheme;
|
|
11
|
+
/** the notebook theme(s); defaults to "air" */
|
|
12
|
+
theme?: NotebookTheme | LightDarkNotebookTheme;
|
|
9
13
|
/** if true, don’t allow editing */
|
|
10
14
|
readOnly?: boolean;
|
|
11
15
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { toNotebook } from "./notebook.js";
|
|
2
2
|
import { isEmpty } from "./text.js";
|
|
3
|
+
const DEFAULT_THEME = "air";
|
|
3
4
|
export function serialize(notebook, { document = globalThis.document } = {}) {
|
|
4
5
|
const _notebook = document.createElement("notebook");
|
|
5
|
-
_notebook.setAttribute("theme", notebook.theme);
|
|
6
|
+
_notebook.setAttribute("theme", serializeTheme(notebook.theme));
|
|
6
7
|
if (notebook.readOnly)
|
|
7
8
|
_notebook.setAttribute("readonly", "");
|
|
8
9
|
_notebook.appendChild(document.createTextNode("\n "));
|
|
@@ -126,8 +127,17 @@ function deserializeFormat(format) {
|
|
|
126
127
|
return format;
|
|
127
128
|
}
|
|
128
129
|
}
|
|
130
|
+
function serializeTheme(theme) {
|
|
131
|
+
return typeof theme === "string" ? theme : `light-dark(${theme.light}, ${theme.dark})`;
|
|
132
|
+
}
|
|
129
133
|
function deserializeTheme(theme) {
|
|
130
|
-
|
|
134
|
+
theme = theme?.trim().toLowerCase() ?? DEFAULT_THEME;
|
|
135
|
+
const match = /^light-dark\(([\w-]+),\s*([\w-]+)\)$/.exec(theme);
|
|
136
|
+
if (match) {
|
|
137
|
+
const [, light, dark] = match;
|
|
138
|
+
return { light, dark };
|
|
139
|
+
}
|
|
140
|
+
return theme;
|
|
131
141
|
}
|
|
132
142
|
function dedent(text) {
|
|
133
143
|
const lines = text.split(/\r\n?|\n/);
|
|
@@ -56,6 +56,7 @@ export declare class NotebookRuntime {
|
|
|
56
56
|
}>;
|
|
57
57
|
topojson: () => Promise<any>;
|
|
58
58
|
vl: () => Promise<any>;
|
|
59
|
+
dark: () => ObservableAsyncGenerator<boolean>;
|
|
59
60
|
now: () => AsyncGenerator<number, void, unknown>;
|
|
60
61
|
width: () => ObservableAsyncGenerator<number>;
|
|
61
62
|
DatabaseClient: () => {
|
|
@@ -108,6 +109,7 @@ export declare class NotebookRuntime {
|
|
|
108
109
|
};
|
|
109
110
|
};
|
|
110
111
|
Mutable: () => typeof import("./stdlib/mutable.js").Mutable;
|
|
112
|
+
Promises: () => typeof import("./stdlib/promises/index.js");
|
|
111
113
|
DOM: () => typeof import("./stdlib/dom/index.js");
|
|
112
114
|
require: () => typeof import("./stdlib/require.js").require;
|
|
113
115
|
__ojs_observer: () => () => import("./stdlib/observer.js").Observer;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** @deprecated */
|
|
2
|
+
export function download(value, name = "untitled", label = "Save") {
|
|
3
|
+
const a = document.createElement("a");
|
|
4
|
+
const b = a.appendChild(document.createElement("button"));
|
|
5
|
+
b.textContent = label;
|
|
6
|
+
a.download = name;
|
|
7
|
+
async function reset() {
|
|
8
|
+
await new Promise(requestAnimationFrame);
|
|
9
|
+
URL.revokeObjectURL(a.href);
|
|
10
|
+
a.removeAttribute("href");
|
|
11
|
+
b.textContent = label;
|
|
12
|
+
b.disabled = false;
|
|
13
|
+
}
|
|
14
|
+
a.onclick = async (event) => {
|
|
15
|
+
b.disabled = true;
|
|
16
|
+
if (a.href)
|
|
17
|
+
return reset(); // Already saved.
|
|
18
|
+
b.textContent = "Saving…";
|
|
19
|
+
try {
|
|
20
|
+
const object = await (typeof value === "function" ? value() : value);
|
|
21
|
+
b.textContent = "Download";
|
|
22
|
+
a.href = URL.createObjectURL(object);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
b.textContent = label;
|
|
26
|
+
}
|
|
27
|
+
if (event.eventPhase)
|
|
28
|
+
return reset(); // Already downloaded.
|
|
29
|
+
b.disabled = false;
|
|
30
|
+
};
|
|
31
|
+
return a;
|
|
32
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const namespaces = {
|
|
2
|
+
math: "http://www.w3.org/1998/Math/MathML",
|
|
3
|
+
svg: "http://www.w3.org/2000/svg",
|
|
4
|
+
xhtml: "http://www.w3.org/1999/xhtml",
|
|
5
|
+
xlink: "http://www.w3.org/1999/xlink",
|
|
6
|
+
xml: "http://www.w3.org/XML/1998/namespace",
|
|
7
|
+
xmlns: "http://www.w3.org/2000/xmlns/"
|
|
8
|
+
};
|
|
9
|
+
function isNamespace(prefix) {
|
|
10
|
+
return Object.prototype.hasOwnProperty.call(namespaces, prefix);
|
|
11
|
+
}
|
|
12
|
+
/** @deprecated */
|
|
13
|
+
export function element(name, attributes) {
|
|
14
|
+
let prefix = (name += "");
|
|
15
|
+
let i = prefix.indexOf(":");
|
|
16
|
+
if (i >= 0 && (prefix = name.slice(0, i)) !== "xmlns")
|
|
17
|
+
name = name.slice(i + 1);
|
|
18
|
+
const element = isNamespace(prefix)
|
|
19
|
+
? document.createElementNS(namespaces[prefix], name)
|
|
20
|
+
: document.createElement(name);
|
|
21
|
+
if (attributes) {
|
|
22
|
+
for (let key in attributes) {
|
|
23
|
+
const value = attributes[key];
|
|
24
|
+
i = (prefix = key).indexOf(":");
|
|
25
|
+
if (i >= 0 && (prefix = key.slice(0, i)) !== "xmlns")
|
|
26
|
+
key = key.slice(i + 1);
|
|
27
|
+
if (isNamespace(prefix))
|
|
28
|
+
element.setAttributeNS(namespaces[prefix], key, value);
|
|
29
|
+
else
|
|
30
|
+
element.setAttribute(key, value);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return element;
|
|
34
|
+
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
export { canvas } from "./canvas.js";
|
|
2
2
|
export { context2d } from "./context2d.js";
|
|
3
|
+
export { download } from "./download.js";
|
|
4
|
+
export { element } from "./element.js";
|
|
5
|
+
export { input } from "./input.js";
|
|
6
|
+
export { range } from "./range.js";
|
|
7
|
+
export { select } from "./select.js";
|
|
3
8
|
export { svg } from "./svg.js";
|
|
4
9
|
export { text } from "./text.js";
|
|
5
10
|
export { uid } from "./uid.js";
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
export { canvas } from "./canvas.js";
|
|
2
2
|
export { context2d } from "./context2d.js";
|
|
3
|
+
export { download } from "./download.js";
|
|
4
|
+
export { element } from "./element.js";
|
|
5
|
+
export { input } from "./input.js";
|
|
6
|
+
export { range } from "./range.js";
|
|
7
|
+
export { select } from "./select.js";
|
|
3
8
|
export { svg } from "./svg.js";
|
|
4
9
|
export { text } from "./text.js";
|
|
5
10
|
export { uid } from "./uid.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function input(type?: HTMLInputElement["type"]): HTMLInputElement;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** @deprecated */
|
|
2
|
+
export function range(min, max, step) {
|
|
3
|
+
if (arguments.length === 1) {
|
|
4
|
+
max = min;
|
|
5
|
+
min = undefined;
|
|
6
|
+
}
|
|
7
|
+
const input = document.createElement("input");
|
|
8
|
+
input.min = String((min = min == null ? 0 : +min));
|
|
9
|
+
input.max = String((max = max == null ? 1 : +max));
|
|
10
|
+
input.step = step == null ? "any" : String((step = +step));
|
|
11
|
+
input.type = "range";
|
|
12
|
+
return input;
|
|
13
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** @deprecated */
|
|
2
|
+
export function select(values) {
|
|
3
|
+
const select = document.createElement("select");
|
|
4
|
+
for (const value of values) {
|
|
5
|
+
const option = document.createElement("option");
|
|
6
|
+
option.value = option.textContent = value;
|
|
7
|
+
select.appendChild(option);
|
|
8
|
+
}
|
|
9
|
+
return select;
|
|
10
|
+
}
|
|
@@ -58,6 +58,7 @@ export interface FileInfo {
|
|
|
58
58
|
lastModified?: number;
|
|
59
59
|
size?: number;
|
|
60
60
|
}
|
|
61
|
+
export declare function requireFileRegistration(value: boolean): void;
|
|
61
62
|
export declare function registerFile(name: string, info: FileInfo | null, base?: string | URL): FileAttachmentImpl | undefined;
|
|
62
63
|
export declare abstract class AbstractFile implements FileAttachment {
|
|
63
64
|
name: string;
|
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
const files = new Map();
|
|
3
|
-
|
|
3
|
+
let strict = false;
|
|
4
4
|
export const FileAttachment = (name, base = document.baseURI) => {
|
|
5
5
|
const href = new URL(name, base).href;
|
|
6
6
|
let file = files.get(href);
|
|
7
7
|
if (!file) {
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
if (strict) {
|
|
9
|
+
throw new Error(`File not found: ${name}`);
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
file = new FileAttachmentImpl(href, name.split("/").pop());
|
|
13
|
+
files.set(href, file);
|
|
14
|
+
}
|
|
10
15
|
}
|
|
11
16
|
return file;
|
|
12
17
|
};
|
|
18
|
+
export function requireFileRegistration(value) {
|
|
19
|
+
strict = value;
|
|
20
|
+
}
|
|
13
21
|
export function registerFile(name, info, base = location.href) {
|
|
14
22
|
const href = new URL(name, base).href;
|
|
15
23
|
if (info == null) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function dark(): ObservableAsyncGenerator<boolean>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { observe } from "./observe.js";
|
|
2
|
+
export function dark() {
|
|
3
|
+
return observe((notify) => {
|
|
4
|
+
let dark;
|
|
5
|
+
const media = matchMedia("(prefers-color-scheme: dark)");
|
|
6
|
+
const probe = document.createElement("div");
|
|
7
|
+
probe.style.transitionProperty = "color, background-color";
|
|
8
|
+
probe.style.transitionDuration = "1ms";
|
|
9
|
+
const changed = () => {
|
|
10
|
+
const s = getComputedStyle(document.body).getPropertyValue("color-scheme").split(/\s+/);
|
|
11
|
+
const d = s.includes("light") && s.includes("dark") ? media.matches : s.includes("dark");
|
|
12
|
+
if (dark === d)
|
|
13
|
+
return;
|
|
14
|
+
notify((dark = d));
|
|
15
|
+
};
|
|
16
|
+
document.body.appendChild(probe);
|
|
17
|
+
changed();
|
|
18
|
+
probe.addEventListener("transitionstart", changed);
|
|
19
|
+
media.addEventListener("change", changed);
|
|
20
|
+
return () => {
|
|
21
|
+
probe.remove();
|
|
22
|
+
media.removeEventListener("change", changed);
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -5,6 +5,7 @@ import * as Generators from "./generators/index.js";
|
|
|
5
5
|
import { Interpreter } from "./interpreter.js";
|
|
6
6
|
import { Mutable } from "./mutable.js";
|
|
7
7
|
import { Observer } from "./observer.js";
|
|
8
|
+
import * as Promises from "./promises/index.js";
|
|
8
9
|
import { require } from "./require.js";
|
|
9
10
|
export declare const root: HTMLElement;
|
|
10
11
|
export declare const library: {
|
|
@@ -45,6 +46,7 @@ export declare const library: {
|
|
|
45
46
|
}>;
|
|
46
47
|
topojson: () => Promise<any>;
|
|
47
48
|
vl: () => Promise<any>;
|
|
49
|
+
dark: () => ObservableAsyncGenerator<boolean>;
|
|
48
50
|
now: () => AsyncGenerator<number, void, unknown>;
|
|
49
51
|
width: () => ObservableAsyncGenerator<number>;
|
|
50
52
|
DatabaseClient: () => {
|
|
@@ -97,6 +99,7 @@ export declare const library: {
|
|
|
97
99
|
};
|
|
98
100
|
};
|
|
99
101
|
Mutable: () => typeof Mutable;
|
|
102
|
+
Promises: () => typeof Promises;
|
|
100
103
|
DOM: () => typeof DOM;
|
|
101
104
|
require: () => typeof require;
|
|
102
105
|
__ojs_observer: () => () => Observer;
|
|
@@ -5,11 +5,13 @@ import * as Generators from "./generators/index.js";
|
|
|
5
5
|
import { Interpreter } from "./interpreter.js";
|
|
6
6
|
import { Mutable } from "./mutable.js";
|
|
7
7
|
import { Observer } from "./observer.js";
|
|
8
|
+
import * as Promises from "./promises/index.js";
|
|
8
9
|
import * as recommendedLibraries from "./recommendedLibraries.js";
|
|
9
10
|
import { require } from "./require.js";
|
|
10
11
|
import * as sampleDatasets from "./sampleDatasets.js";
|
|
11
12
|
export const root = document.querySelector("main") ?? document.body;
|
|
12
13
|
export const library = {
|
|
14
|
+
dark: () => Generators.dark(),
|
|
13
15
|
now: () => Generators.now(),
|
|
14
16
|
width: () => Generators.width(root),
|
|
15
17
|
DatabaseClient: () => DatabaseClient,
|
|
@@ -17,6 +19,7 @@ export const library = {
|
|
|
17
19
|
Generators: () => Generators,
|
|
18
20
|
Interpreter: () => Interpreter,
|
|
19
21
|
Mutable: () => Mutable,
|
|
22
|
+
Promises: () => Promises, // deprecated!
|
|
20
23
|
DOM: () => DOM, // deprecated!
|
|
21
24
|
require: () => require, // deprecated!
|
|
22
25
|
__ojs_observer: () => () => new Observer(),
|
|
@@ -13,19 +13,19 @@ export function MarkdownRenderer({ document = window.document } = {}) {
|
|
|
13
13
|
let fragment = null;
|
|
14
14
|
let partIndex = -1;
|
|
15
15
|
const parts = [];
|
|
16
|
-
// Concatenate the text using
|
|
16
|
+
// Concatenate the text using anchors as placeholders.
|
|
17
17
|
for (let i = 0, n = values.length; i < n; ++i) {
|
|
18
18
|
const value = values[i];
|
|
19
19
|
if (value instanceof Node) {
|
|
20
20
|
parts[++partIndex] = value;
|
|
21
|
-
source +=
|
|
21
|
+
source += `<a id=o:${partIndex}></a>`;
|
|
22
22
|
}
|
|
23
23
|
else if (Array.isArray(value)) {
|
|
24
24
|
for (const node of value) {
|
|
25
25
|
if (node instanceof Node) {
|
|
26
26
|
if (fragment === null) {
|
|
27
27
|
parts[++partIndex] = fragment = document.createDocumentFragment();
|
|
28
|
-
source +=
|
|
28
|
+
source += `<a id=o:${partIndex}></a>`;
|
|
29
29
|
}
|
|
30
30
|
fragment.appendChild(node);
|
|
31
31
|
}
|
|
@@ -44,14 +44,12 @@ export function MarkdownRenderer({ document = window.document } = {}) {
|
|
|
44
44
|
// Render the text.
|
|
45
45
|
const root = document.createElement("div");
|
|
46
46
|
root.innerHTML = mi.render(source);
|
|
47
|
-
// Walk the rendered content to replace
|
|
47
|
+
// Walk the rendered content to replace anchor placeholders.
|
|
48
48
|
if (++partIndex > 0) {
|
|
49
49
|
const nodes = new Array(partIndex);
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (/^o:\d+$/.test(node.nodeValue)) {
|
|
54
|
-
nodes[+node.nodeValue.slice(2)] = node;
|
|
50
|
+
for (const node of root.querySelectorAll("a[id^='o:']")) {
|
|
51
|
+
if (/^o:\d+$/.test(node.id)) {
|
|
52
|
+
nodes[+node.id.slice(2)] = node;
|
|
55
53
|
}
|
|
56
54
|
}
|
|
57
55
|
for (let i = 0; i < partIndex; ++i) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const timeouts = new Map();
|
|
2
|
+
/** @deprecated */
|
|
3
|
+
export async function when(time, value) {
|
|
4
|
+
let timeout = timeouts.get((time = +time));
|
|
5
|
+
if (!timeout) {
|
|
6
|
+
const now = Date.now();
|
|
7
|
+
const delay = time - now;
|
|
8
|
+
if (delay > 0) {
|
|
9
|
+
if (delay > 0x7fffffff)
|
|
10
|
+
throw new Error("too long to wait");
|
|
11
|
+
timeout = new Promise((resolve) => setTimeout(resolve, delay));
|
|
12
|
+
timeout.then(() => timeouts.delete(time));
|
|
13
|
+
timeouts.set(time, timeout);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
await timeout;
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
require.resolve = resolve;
|
|
2
|
+
const cache = new WeakMap();
|
|
2
3
|
export function require(...specifiers) {
|
|
3
4
|
return specifiers.length === 1
|
|
4
|
-
? import(/* @vite-ignore */ resolve(specifiers[0]))
|
|
5
|
-
: Promise.all(specifiers.map((s) => require(s))).then((modules) => Object.assign({}, ...modules));
|
|
5
|
+
? import(/* @vite-ignore */ resolve(specifiers[0])).then(objectify)
|
|
6
|
+
: Promise.all(specifiers.map((s) => require(s))).then((modules) => Object.assign({}, ...modules)); // prettier-ignore
|
|
6
7
|
}
|
|
7
8
|
function parseNpmSpecifier(specifier) {
|
|
8
9
|
const parts = specifier.split("/");
|
|
@@ -22,6 +23,13 @@ function resolve(_specifier) {
|
|
|
22
23
|
const { name, range, path } = parseNpmSpecifier(specifier);
|
|
23
24
|
return `https://cdn.jsdelivr.net/npm/${name}${range}${path + (isFile(path) || isDirectory(path) ? "" : "/+esm")}`;
|
|
24
25
|
}
|
|
26
|
+
/** Allow mutation of required modules while maintaining a canonical instance; ugly! */
|
|
27
|
+
function objectify(module) {
|
|
28
|
+
let object = cache.get(module);
|
|
29
|
+
if (!object)
|
|
30
|
+
cache.set(module, (object = { ...module }));
|
|
31
|
+
return object;
|
|
32
|
+
}
|
|
25
33
|
/** Returns true for e.g. https://example.com/ */
|
|
26
34
|
function isProtocol(specifier) {
|
|
27
35
|
return /^\w+:/.test(specifier);
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
:root {
|
|
2
|
-
--serif:
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
--sans-serif:
|
|
6
|
-
"Inter", -apple-system, BlinkMacSystemFont, "avenir next", avenir, helvetica, "helvetica neue",
|
|
7
|
-
ubuntu, roboto, noto, "segoe ui", arial, sans-serif;
|
|
8
|
-
--monospace: "Spline Sans Mono", Menlo, Consolas, monospace;
|
|
2
|
+
--serif: "Source Serif 4 Variable", ui-serif, serif;
|
|
3
|
+
--sans-serif: "Inter Variable", ui-sans-serif, sans-serif;
|
|
4
|
+
--monospace: "Spline Sans Mono Variable", Menlo, Consolas, monospace;
|
|
9
5
|
--monospace-font: 14px/1.5 var(--monospace);
|
|
10
6
|
--max-width: calc(1024px - 4 * 16px); /* max-w-5xl p-4 */
|
|
11
7
|
}
|
|
@@ -5,24 +5,12 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<style type="text/css">
|
|
7
7
|
@import url("observable:styles/index.css");
|
|
8
|
-
@import url("@fontsource
|
|
9
|
-
@import url("@fontsource/
|
|
10
|
-
@import url("@fontsource/
|
|
11
|
-
@import url("@fontsource/
|
|
12
|
-
@import url("@fontsource/spline-sans-mono/
|
|
13
|
-
@import url("@fontsource/spline-sans-mono/
|
|
14
|
-
@import url("@fontsource/source-serif-4/400.css");
|
|
15
|
-
@import url("@fontsource/source-serif-4/700.css");
|
|
16
|
-
@import url("@fontsource/source-serif-4/400-italic.css");
|
|
17
|
-
@import url("@fontsource/source-serif-4/700-italic.css");
|
|
18
|
-
@import url("@fontsource/inter/400.css");
|
|
19
|
-
@import url("@fontsource/inter/500.css");
|
|
20
|
-
@import url("@fontsource/inter/600.css");
|
|
21
|
-
@import url("@fontsource/inter/700.css");
|
|
22
|
-
@import url("@fontsource/inter/400-italic.css");
|
|
23
|
-
@import url("@fontsource/inter/500-italic.css");
|
|
24
|
-
@import url("@fontsource/inter/600-italic.css");
|
|
25
|
-
@import url("@fontsource/inter/700-italic.css");
|
|
8
|
+
@import url("@fontsource-variable/inter/wght.css");
|
|
9
|
+
@import url("@fontsource-variable/inter/wght-italic.css");
|
|
10
|
+
@import url("@fontsource-variable/source-serif-4/wght.css");
|
|
11
|
+
@import url("@fontsource-variable/source-serif-4/wght-italic.css");
|
|
12
|
+
@import url("@fontsource-variable/spline-sans-mono/wght.css");
|
|
13
|
+
@import url("@fontsource-variable/spline-sans-mono/wght-italic.css");
|
|
26
14
|
</style>
|
|
27
15
|
</head>
|
|
28
16
|
<body>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { fork, spawn } from "node:child_process";
|
|
2
|
-
import { createWriteStream, existsSync } from "node:fs";
|
|
2
|
+
import { createWriteStream, existsSync, unlinkSync } from "node:fs";
|
|
3
3
|
import { mkdir, readFile } from "node:fs/promises";
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
5
5
|
import { relative } from "node:path/posix";
|
|
@@ -78,7 +78,12 @@ export function observable({ window = new JSDOM().window, parser = new window.DO
|
|
|
78
78
|
const child = fork(fileURLToPath(import.meta.resolve("../../bin/query.js")), args);
|
|
79
79
|
await new Promise((resolve, reject) => {
|
|
80
80
|
child.on("error", reject);
|
|
81
|
-
child.on("exit",
|
|
81
|
+
child.on("exit", (code) => {
|
|
82
|
+
if (code === 0)
|
|
83
|
+
return resolve();
|
|
84
|
+
unlinkSync(cachePath); // don’t pollute cache with failure
|
|
85
|
+
reject(new Error(`${cell.database} query exited with ${code}`));
|
|
86
|
+
});
|
|
82
87
|
});
|
|
83
88
|
}
|
|
84
89
|
cell.mode = "js";
|
|
@@ -98,7 +103,12 @@ export function observable({ window = new JSDOM().window, parser = new window.DO
|
|
|
98
103
|
child.stdout.pipe(createWriteStream(cachePath));
|
|
99
104
|
await new Promise((resolve, reject) => {
|
|
100
105
|
child.on("error", reject);
|
|
101
|
-
child.on("exit",
|
|
106
|
+
child.on("exit", (code) => {
|
|
107
|
+
if (code === 0)
|
|
108
|
+
return resolve();
|
|
109
|
+
unlinkSync(cachePath); // don’t pollute cache with failure
|
|
110
|
+
reject(new Error(`${mode} interpreter exited with ${code}`));
|
|
111
|
+
});
|
|
102
112
|
});
|
|
103
113
|
}
|
|
104
114
|
if (format === "html" && !hidden) {
|
|
@@ -127,8 +137,12 @@ export function observable({ window = new JSDOM().window, parser = new window.DO
|
|
|
127
137
|
throw new Error("body not found");
|
|
128
138
|
return (`<!doctype html>` +
|
|
129
139
|
output.slice(0, i) +
|
|
130
|
-
`<style type="text/css"
|
|
131
|
-
|
|
140
|
+
`<style type="text/css">${typeof notebook.theme === "string"
|
|
141
|
+
? `
|
|
142
|
+
@import url("observable:styles/theme-${notebook.theme}.css");`
|
|
143
|
+
: `
|
|
144
|
+
@import url("observable:styles/theme-${notebook.theme.light}.css") (prefers-color-scheme: light);
|
|
145
|
+
@import url("observable:styles/theme-${notebook.theme.dark}.css") (prefers-color-scheme: dark);`}
|
|
132
146
|
</style><script type="module">
|
|
133
147
|
import {define} from "observable:runtime";${Array.from(assets)
|
|
134
148
|
.map((asset, i) => `
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/observablehq/notebook-kit.git"
|
|
7
7
|
},
|
|
8
|
-
"version": "1.
|
|
8
|
+
"version": "1.6.0",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"scripts": {
|
|
11
11
|
"test": "vitest",
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
"./*.css": "./dist/src/styles/*.css"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@fontsource/inter": "^5.2.
|
|
46
|
-
"@fontsource/source-serif-4": "^5.2.
|
|
47
|
-
"@fontsource/spline-sans-mono": "^5.2.
|
|
45
|
+
"@fontsource-variable/inter": "^5.2.8",
|
|
46
|
+
"@fontsource-variable/source-serif-4": "^5.2.9",
|
|
47
|
+
"@fontsource-variable/spline-sans-mono": "^5.2.8",
|
|
48
48
|
"@lezer/common": "^1.2.3",
|
|
49
49
|
"@lezer/css": "^1.2.1",
|
|
50
50
|
"@lezer/highlight": "^1.2.1",
|
|
@@ -65,28 +65,28 @@
|
|
|
65
65
|
"vite": "^7.0.0"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
-
"@databricks/sql": "^1.
|
|
68
|
+
"@databricks/sql": "^1.14.0",
|
|
69
69
|
"@duckdb/node-api": "^1.3.2-alpha.26",
|
|
70
70
|
"@eslint/js": "^9.29.0",
|
|
71
|
-
"@google-cloud/bigquery": "^8.
|
|
71
|
+
"@google-cloud/bigquery": "^8.3.0",
|
|
72
72
|
"@types/jsdom": "^21.1.7",
|
|
73
73
|
"@types/markdown-it": "^14.1.2",
|
|
74
74
|
"bun-types": "^1.2.20",
|
|
75
75
|
"eslint": "^9.29.0",
|
|
76
76
|
"globals": "^16.2.0",
|
|
77
77
|
"htl": "^0.3.1",
|
|
78
|
-
"postgres": "^3.4.
|
|
79
|
-
"snowflake-sdk": "^2.
|
|
78
|
+
"postgres": "^3.4.9",
|
|
79
|
+
"snowflake-sdk": "^2.4.0",
|
|
80
80
|
"tsx": "^4.20.3",
|
|
81
81
|
"typescript-eslint": "^8.35.0",
|
|
82
82
|
"vitest": "^3.2.4"
|
|
83
83
|
},
|
|
84
84
|
"peerDependencies": {
|
|
85
|
-
"@databricks/sql": "^1.
|
|
85
|
+
"@databricks/sql": "^1.14.0",
|
|
86
86
|
"@duckdb/node-api": "^1.3.2-alpha.26",
|
|
87
|
-
"@google-cloud/bigquery": "^8.
|
|
88
|
-
"postgres": "^3.4.
|
|
89
|
-
"snowflake-sdk": "^2.
|
|
87
|
+
"@google-cloud/bigquery": "^8.3.0",
|
|
88
|
+
"postgres": "^3.4.9",
|
|
89
|
+
"snowflake-sdk": "^2.4.0"
|
|
90
90
|
},
|
|
91
91
|
"peerDependenciesMeta": {
|
|
92
92
|
"@databricks/sql": {
|