@tlockney/cdp-kernel 0.8.0 → 0.9.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.
@@ -5,31 +5,230 @@ export const WIDGET_PROTOCOL_VERSION: "2.1.0";
5
5
 
6
6
  export type WidgetState = { [key: string]: unknown };
7
7
 
8
+ /** Options for `jupyter.widget`. */
8
9
  export interface WidgetOptions {
9
- /** The anywidget ES module: `export default { render({ model, el }) { ... } }`. */
10
+ /**
11
+ * The anywidget view module, as source:
12
+ * `export default { render({ model, el }) { ... } }`. A `${...}` inside
13
+ * a template literal here is filled in by the kernel, not the browser:
14
+ * escape it as `\${...}`.
15
+ */
10
16
  esm: string;
11
- /** Optional CSS for the view. */
17
+ /** CSS for the view. */
12
18
  css?: string;
13
- /** Initial synced state. Uint8Array/ArrayBuffer values travel as binary buffers. */
19
+ /**
20
+ * The initial synced state. `Uint8Array` and `ArrayBuffer` values travel
21
+ * as binary buffers. Default `{}`.
22
+ */
14
23
  state?: WidgetState;
15
24
  }
16
25
 
17
- /** An anywidget model, synced with its frontend views. Display it to show a view. */
26
+ /**
27
+ * An anywidget model, from `jupyter.widget`, synced with its frontend
28
+ * views. Display it to show a view; display it again for a second view of
29
+ * the same model.
30
+ */
18
31
  export interface Widget {
19
32
  /** The model id (the comm id). */
20
33
  readonly id: string;
34
+ /** Whether the model has been closed. */
21
35
  readonly closed: boolean;
36
+ /**
37
+ * The current value of a state key.
38
+ *
39
+ * @param key The state key.
40
+ * @returns Its value, or `undefined`.
41
+ * @example
42
+ * ```ts
43
+ * const w = jupyter.widget({ esm: "export default {}", state: { count: 0 } });
44
+ * w.get("count"); // 0
45
+ * ```
46
+ */
22
47
  get(key: string): unknown;
23
- /** Sets one key or several, and syncs the change to the frontend. */
48
+ /**
49
+ * Change one state key, or several at once, and sync the change to every
50
+ * view. Fires this model's own `change` listeners too. After `close` it
51
+ * changes the local state only.
52
+ *
53
+ * @param key The state key.
54
+ * @param value Its new value.
55
+ * @param changes Several keys and their new values, instead of `key` and
56
+ * `value`.
57
+ * @example
58
+ * ```ts
59
+ * const w = jupyter.widget({ esm: "export default {}", state: { count: 0, label: "" } });
60
+ * w.set("count", 1);
61
+ * w.set({ count: 2, label: "two" });
62
+ * ```
63
+ * @example
64
+ * ```ts
65
+ * const progress = jupyter.widget({
66
+ * esm: `export default {
67
+ * render({ model, el }) {
68
+ * el.innerHTML =
69
+ * '<div class="progress-track"><div class="progress-fill"></div></div>' +
70
+ * '<div class="progress-label"></div>';
71
+ * const fill = el.querySelector(".progress-fill");
72
+ * const label = el.querySelector(".progress-label");
73
+ * const update = () => {
74
+ * const percent = Math.round(model.get("value") * 100);
75
+ * fill.style.width = percent + "%";
76
+ * label.textContent = model.get("message") + " (" + percent + "%)";
77
+ * };
78
+ * model.on("change:value", update);
79
+ * model.on("change:message", update);
80
+ * update();
81
+ * },
82
+ * };`,
83
+ * css: `
84
+ * .progress-track { width: 320px; height: 10px; border-radius: 5px; background: #e5e7eb; overflow: hidden; }
85
+ * .progress-fill { height: 100%; background: #2e6e64; transition: width 0.2s; }
86
+ * .progress-label { margin-top: 6px; font: 13px system-ui, sans-serif; color: #444; }
87
+ * `,
88
+ * state: { value: 0, message: "Starting" },
89
+ * });
90
+ * await jupyter.display(progress);
91
+ *
92
+ * const files = ["january.csv", "february.csv", "march.csv", "april.csv", "may.csv"];
93
+ * for (const [i, file] of files.entries()) {
94
+ * progress.set({ message: "Processing " + file, value: i / files.length });
95
+ * await new Promise((resolve) => setTimeout(resolve, 600)); // stand-in for real work
96
+ * }
97
+ * progress.set({ message: "Done", value: 1 });
98
+ * ```
99
+ * ![A progress bar three fifths full, labelled "Processing april.csv (60%)", captured while the cell was still running.](https://cdp-kernel.org/images/widgets/progress.png)
100
+ */
24
101
  set(key: string, value: unknown): void;
25
102
  set(changes: WidgetState): void;
103
+ /**
104
+ * Listen for state changes or custom messages from the views.
105
+ * `change:key` gets the new and previous value; `change` gets all the
106
+ * changed keys; `msg:custom` gets a view's `model.send` content.
107
+ *
108
+ * @param event `change:<key>`, `change` or `msg:custom`.
109
+ * @param listener Called for each event; may be async.
110
+ * @example
111
+ * ```ts
112
+ * const w = jupyter.widget({ esm: "export default {}", state: { count: 0 } });
113
+ * w.on("change:count", (value, previous) => console.log(previous, "->", value));
114
+ * w.on("msg:custom", (content) => console.log("view sent", content));
115
+ * ```
116
+ * @example
117
+ * ```ts
118
+ * const temperature = jupyter.widget({
119
+ * esm: `export default {
120
+ * render({ model, el }) {
121
+ * const slider = document.createElement("input");
122
+ * slider.type = "range";
123
+ * slider.min = "-20";
124
+ * slider.max = "40";
125
+ * const label = document.createElement("span");
126
+ * const update = () => {
127
+ * slider.value = String(model.get("celsius"));
128
+ * label.textContent = " " + model.get("celsius") + " °C is " + model.get("fahrenheit") + " °F";
129
+ * };
130
+ * slider.oninput = () => {
131
+ * model.set("celsius", slider.valueAsNumber);
132
+ * model.save_changes();
133
+ * };
134
+ * model.on("change:celsius", update);
135
+ * model.on("change:fahrenheit", update);
136
+ * update();
137
+ * el.append(slider, label);
138
+ * },
139
+ * };`,
140
+ * state: { celsius: 20, fahrenheit: 68 },
141
+ * });
142
+ *
143
+ * temperature.on("change:celsius", (celsius) => {
144
+ * temperature.set("fahrenheit", Math.round((Number(celsius) * 9) / 5 + 32));
145
+ * });
146
+ *
147
+ * temperature;
148
+ * ```
149
+ * ![A range slider moved to 30, labelled "30 °C is 86 °F".](https://cdp-kernel.org/images/widgets/temperature.png)
150
+ */
26
151
  on(event: `change:${string}`, listener: (value: unknown, previous: unknown) => void | Promise<void>): void;
27
152
  on(event: "change", listener: (changes: WidgetState) => void | Promise<void>): void;
28
153
  on(event: "msg:custom", listener: (content: unknown, buffers: Uint8Array[]) => void | Promise<void>): void;
154
+ /**
155
+ * Remove a listener added with `on`.
156
+ *
157
+ * @param event The event it was added for.
158
+ * @param listener The same function.
159
+ * @example
160
+ * ```ts
161
+ * const w = jupyter.widget({ esm: "export default {}", state: { count: 0 } });
162
+ * const log = (value: unknown) => console.log(value);
163
+ * w.on("change:count", log);
164
+ * w.off("change:count", log);
165
+ * ```
166
+ */
29
167
  off(event: string, listener: (...args: never[]) => unknown): void;
30
- /** A custom message to the view's `model.on("msg:custom", ...)`. */
168
+ /**
169
+ * Send a custom message to every view's `model.on("msg:custom", ...)`.
170
+ *
171
+ * @param content JSON content.
172
+ * @param buffers Binary buffers. Default `[]`.
173
+ * @example
174
+ * ```ts
175
+ * const w = jupyter.widget({ esm: "export default {}" });
176
+ * w.send({ type: "reset" });
177
+ * ```
178
+ * @example
179
+ * ```ts
180
+ * const dice = jupyter.widget({
181
+ * esm: `export default {
182
+ * render({ model, el }) {
183
+ * const button = document.createElement("button");
184
+ * button.textContent = "Roll 3 dice";
185
+ * button.onclick = () => model.send({ count: 3 });
186
+ * const result = document.createElement("span");
187
+ * result.style.marginLeft = "12px";
188
+ * result.style.fontSize = "24px";
189
+ * model.on("msg:custom", (message) => {
190
+ * result.textContent = message.faces + " (total " + message.total + ")";
191
+ * });
192
+ * el.append(button, result);
193
+ * },
194
+ * };`,
195
+ * });
196
+ *
197
+ * dice.on("msg:custom", (content) => {
198
+ * const { count } = content as { count: number };
199
+ * const rolls = Array.from({ length: count }, () => 1 + Math.floor(Math.random() * 6));
200
+ * dice.send({
201
+ * faces: rolls.map((n) => "⚀⚁⚂⚃⚄⚅"[n - 1]).join(" "),
202
+ * total: rolls.reduce((a, b) => a + b, 0),
203
+ * });
204
+ * });
205
+ *
206
+ * dice;
207
+ * ```
208
+ * ![A "Roll 3 dice" button next to three die faces and their total, the kernel's answer to a click.](https://cdp-kernel.org/images/widgets/dice.png)
209
+ */
31
210
  send(content: unknown, buffers?: Uint8Array[]): void;
211
+ /**
212
+ * Close the model. Its views stop syncing.
213
+ *
214
+ * @example
215
+ * ```ts
216
+ * const w = jupyter.widget({ esm: "export default {}" });
217
+ * w.close();
218
+ * ```
219
+ */
32
220
  close(): void;
221
+ /**
222
+ * The full state, as the frontend sees it: anywidget's model keys
223
+ * (`_model_name`, `_view_name` and the rest) and the widget's own.
224
+ *
225
+ * @returns Every state key and its value.
226
+ * @example
227
+ * ```ts
228
+ * const w = jupyter.widget({ esm: "export default {}", state: { count: 0 } });
229
+ * w.fullState(); // { _model_name: "AnyModel", _model_module: "anywidget", ..., count: 0 }
230
+ * ```
231
+ */
33
232
  fullState(): WidgetState;
34
233
  /** Displaying a widget shows a view of it (application/vnd.jupyter.widget-view+json). */
35
234
  [$display](): MediaBundle;