@marimo-team/islands 0.18.2 → 0.18.4

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.
Files changed (43) hide show
  1. package/dist/{constants-DWBOe162.js → constants-D_G8vnDk.js} +5 -4
  2. package/dist/{formats-7RSCCoSI.js → formats-Bi_tbdwB.js} +21 -22
  3. package/dist/{glide-data-editor-D-Ia_Jsv.js → glide-data-editor-DXF8E-QD.js} +2 -2
  4. package/dist/main.js +280 -148
  5. package/dist/style.css +1 -1
  6. package/dist/{types-Dunk85GC.js → types-DclGb0Yh.js} +1 -1
  7. package/dist/{vega-component-kU4hFYYJ.js → vega-component-BFcH2SqR.js} +8 -8
  8. package/package.json +1 -1
  9. package/src/components/app-config/user-config-form.tsx +14 -1
  10. package/src/components/data-table/context-menu.tsx +7 -3
  11. package/src/components/data-table/filter-pills.tsx +2 -1
  12. package/src/components/data-table/filters.ts +11 -2
  13. package/src/components/editor/cell/CreateCellButton.tsx +5 -3
  14. package/src/components/editor/cell/collapse.tsx +2 -2
  15. package/src/components/editor/chrome/components/contribute-snippet-button.tsx +22 -103
  16. package/src/components/editor/controls/duplicate-shortcut-banner.tsx +50 -0
  17. package/src/components/editor/controls/keyboard-shortcuts.tsx +25 -2
  18. package/src/components/editor/notebook-banner.tsx +1 -1
  19. package/src/components/editor/notebook-cell.tsx +4 -3
  20. package/src/components/editor/output/__tests__/ansi-reduce.test.ts +6 -6
  21. package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +3 -3
  22. package/src/components/pages/home-page.tsx +6 -0
  23. package/src/components/scratchpad/scratchpad.tsx +2 -1
  24. package/src/core/constants.ts +10 -0
  25. package/src/core/layout/useTogglePresenting.ts +69 -25
  26. package/src/core/state/__mocks__/mocks.ts +1 -0
  27. package/src/hooks/__tests__/useDuplicateShortcuts.test.ts +449 -0
  28. package/src/hooks/useDuplicateShortcuts.ts +145 -0
  29. package/src/plugins/impl/NumberPlugin.tsx +1 -1
  30. package/src/plugins/impl/__tests__/NumberPlugin.test.tsx +1 -1
  31. package/src/plugins/impl/anywidget/AnyWidgetPlugin.tsx +67 -47
  32. package/src/plugins/impl/anywidget/__tests__/AnyWidgetPlugin.test.tsx +2 -57
  33. package/src/plugins/impl/anywidget/__tests__/model.test.ts +23 -19
  34. package/src/plugins/impl/anywidget/model.ts +68 -41
  35. package/src/plugins/impl/data-frames/utils/__tests__/operators.test.ts +2 -0
  36. package/src/plugins/impl/data-frames/utils/operators.ts +1 -0
  37. package/src/plugins/impl/vega/vega.css +5 -0
  38. package/src/plugins/layout/NavigationMenuPlugin.tsx +24 -22
  39. package/src/plugins/layout/StatPlugin.tsx +43 -23
  40. package/src/utils/__tests__/data-views.test.ts +495 -13
  41. package/src/utils/__tests__/json-parser.test.ts +1 -1
  42. package/src/utils/data-views.ts +134 -16
  43. package/src/utils/json/base64.ts +8 -0
@@ -1,19 +1,131 @@
1
1
  /* Copyright 2024 Marimo. All rights reserved. */
2
- import { set } from "lodash-es";
2
+ import { get, set } from "lodash-es";
3
3
  import { invariant } from "./invariant";
4
+ import {
5
+ type Base64String,
6
+ binaryToByteString,
7
+ byteStringToBinary,
8
+ typedAtob,
9
+ typedBtoa,
10
+ } from "./json/base64";
4
11
  import { Logger } from "./Logger";
5
12
 
6
13
  /**
7
- * Update the object with DataView buffers at the specified paths.
14
+ * Convert a DataView to a base64 string.
8
15
  */
9
- export function updateBufferPaths<T extends Record<string, unknown>>(
16
+ export function dataViewToBase64(dataView: DataView): Base64String {
17
+ const bytes = new Uint8Array(
18
+ dataView.buffer,
19
+ dataView.byteOffset,
20
+ dataView.byteLength,
21
+ );
22
+ const byteString = binaryToByteString(bytes);
23
+ return typedBtoa(byteString);
24
+ }
25
+
26
+ /**
27
+ * Recursively find all DataViews in an object and return their paths.
28
+ *
29
+ * This mirrors ipywidgets' _separate_buffers logic.
30
+ */
31
+ function findDataViewPaths(
32
+ obj: unknown,
33
+ currentPath: (string | number)[] = [],
34
+ ): (string | number)[][] {
35
+ const paths: (string | number)[][] = [];
36
+
37
+ if (obj instanceof DataView) {
38
+ paths.push(currentPath);
39
+ } else if (Array.isArray(obj)) {
40
+ for (let i = 0; i < obj.length; i++) {
41
+ paths.push(...findDataViewPaths(obj[i], [...currentPath, i]));
42
+ }
43
+ } else if (obj !== null && typeof obj === "object") {
44
+ for (const [key, value] of Object.entries(obj)) {
45
+ paths.push(...findDataViewPaths(value, [...currentPath, key]));
46
+ }
47
+ }
48
+
49
+ return paths;
50
+ }
51
+
52
+ /**
53
+ * Serialize DataView buffers to base64 strings.
54
+ *
55
+ * Finds all DataViews in the object.
56
+ */
57
+ export function serializeBuffersToBase64<T extends Record<string, unknown>>(
10
58
  inputObject: T,
11
- bufferPaths: readonly (readonly (string | number)[])[],
12
- buffers: readonly DataView[],
13
- ): T {
14
- // If no buffer paths, return the original object
59
+ ): WireFormat<T> {
60
+ // Dynamically find all DataView paths instead of using fixed bufferPaths
61
+ const dataViewPaths = findDataViewPaths(inputObject);
62
+
63
+ if (dataViewPaths.length === 0) {
64
+ return { state: inputObject, buffers: [], bufferPaths: [] };
65
+ }
66
+
67
+ const state = structuredClone(inputObject);
68
+ const buffers: Base64String[] = [];
69
+ const bufferPaths: (string | number)[][] = [];
70
+
71
+ for (const bufferPath of dataViewPaths) {
72
+ const dataView = get(inputObject, bufferPath);
73
+ if (dataView instanceof DataView) {
74
+ const base64 = dataViewToBase64(dataView);
75
+ buffers.push(base64);
76
+ bufferPaths.push(bufferPath);
77
+ set(state, bufferPath, base64);
78
+ }
79
+ }
80
+
81
+ return { state, buffers, bufferPaths };
82
+ }
83
+
84
+ /**
85
+ * Wire format for anywidget state with binary data.
86
+ * Buffers can be either base64 strings (from network) or DataViews (in-memory).
87
+ */
88
+ export interface WireFormat<T = Record<string, unknown>> {
89
+ state: T;
90
+ bufferPaths: (string | number)[][];
91
+ buffers: Base64String[];
92
+ }
93
+
94
+ /**
95
+ * Check if an object is in wire format.
96
+ */
97
+ export function isWireFormat<T = Record<string, unknown>>(
98
+ obj: unknown,
99
+ ): obj is WireFormat<T> {
100
+ return (
101
+ obj !== null &&
102
+ typeof obj === "object" &&
103
+ "state" in obj &&
104
+ "bufferPaths" in obj &&
105
+ "buffers" in obj
106
+ );
107
+ }
108
+
109
+ /**
110
+ * Decode wire format or insert DataViews at specified paths.
111
+ *
112
+ * Accepts either:
113
+ * 1. Wire format: { state, bufferPaths, buffers } where buffers are base64 strings
114
+ * 2. Direct format: { state, bufferPaths, buffers } where buffers are DataViews
115
+ *
116
+ * For ndarray-like structures {view: null, dtype, shape}, we insert the DataView
117
+ * at the 'view' key, preserving the structure for round-tripping.
118
+ */
119
+ export function decodeFromWire<T extends Record<string, unknown>>(input: {
120
+ state: T;
121
+ bufferPaths?: (string | number)[][];
122
+ buffers?: readonly (DataView | Base64String)[];
123
+ }): T {
124
+ const { state, bufferPaths, buffers } = input;
125
+
126
+ // If no buffer paths, return the original state
15
127
  if (!bufferPaths || bufferPaths.length === 0) {
16
- return inputObject;
128
+ return state;
17
129
  }
18
130
 
19
131
  // If has buffers, assert they are the same size
@@ -24,18 +136,24 @@ export function updateBufferPaths<T extends Record<string, unknown>>(
24
136
  );
25
137
  }
26
138
 
27
- let object = structuredClone(inputObject);
139
+ const out = structuredClone(state);
28
140
 
29
141
  for (const [i, bufferPath] of bufferPaths.entries()) {
30
- // If buffers exists, we use that value
31
- // Otherwise we grab it from inside the inputObject
32
- const dataView = buffers[i];
33
- if (!dataView) {
34
- Logger.warn("Could not find buffer at path", bufferPath);
142
+ const buffer = buffers?.[i];
143
+
144
+ if (buffer == null) {
145
+ Logger.warn("[anywidget] Could not find buffer at path", bufferPath);
35
146
  continue;
36
147
  }
37
- object = set(object, bufferPath, dataView);
148
+
149
+ // Handle both base64 strings (from wire format) and DataViews (direct usage)
150
+ if (typeof buffer === "string") {
151
+ const bytes = byteStringToBinary(typedAtob(buffer));
152
+ set(out, bufferPath, new DataView(bytes.buffer));
153
+ } else {
154
+ set(out, bufferPath, buffer);
155
+ }
38
156
  }
39
157
 
40
- return object;
158
+ return out;
41
159
  }
@@ -55,6 +55,14 @@ export function byteStringToBinary(bytes: ByteString): Uint8Array {
55
55
  return Uint8Array.from(bytes, (c) => c.charCodeAt(0));
56
56
  }
57
57
 
58
+ export function binaryToByteString(binary: Uint8Array): ByteString {
59
+ let result = "";
60
+ for (const byte of binary) {
61
+ result += String.fromCharCode(byte);
62
+ }
63
+ return result as ByteString;
64
+ }
65
+
58
66
  export function safeExtractSetUIElementMessageBuffers(
59
67
  op: OperationMessageData<"send-ui-element-message">,
60
68
  ): readonly DataView[] {