@marimo-team/frontend 0.23.2-dev53 → 0.23.2-dev54

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/index.html CHANGED
@@ -66,7 +66,7 @@
66
66
  <marimo-server-token data-token="{{ server_token }}" hidden></marimo-server-token>
67
67
  <!-- /TODO -->
68
68
  <title>{{ title }}</title>
69
- <script type="module" crossorigin src="./assets/index-9Au3XWKl.js"></script>
69
+ <script type="module" crossorigin src="./assets/index-DhasKuQs.js"></script>
70
70
  <link rel="modulepreload" crossorigin href="./assets/preload-helper-BFv3hW2s.js">
71
71
  <link rel="modulepreload" crossorigin href="./assets/chunk-LvLJmgfZ.js">
72
72
  <link rel="modulepreload" crossorigin href="./assets/react-Bj1aDYRI.js">
@@ -243,7 +243,7 @@
243
243
  <link rel="stylesheet" crossorigin href="./assets/cells-jmgGt1lS.css">
244
244
  <link rel="stylesheet" crossorigin href="./assets/markdown-renderer-DdDKmWlR.css">
245
245
  <link rel="stylesheet" crossorigin href="./assets/JsonOutput-B7vuddcd.css">
246
- <link rel="stylesheet" crossorigin href="./assets/index-CeoQ1Qbe.css">
246
+ <link rel="stylesheet" crossorigin href="./assets/index-4VEstEY3.css">
247
247
  </head>
248
248
  <body>
249
249
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/frontend",
3
- "version": "0.23.2-dev53",
3
+ "version": "0.23.2-dev54",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
- import { CornerLeftUp } from "lucide-react";
3
+ import { type LucideIcon, CornerLeftUp } from "lucide-react";
4
4
  import { type JSX, useEffect, useState } from "react";
5
5
  import { z } from "zod";
6
6
  import {
@@ -128,6 +128,43 @@ interface FileBrowserProps extends Data, PluginFunctions {
128
128
  host: HTMLElement;
129
129
  }
130
130
 
131
+ interface CheckboxOrIconProps {
132
+ isSelected: boolean;
133
+ canSelect: boolean;
134
+ Icon: LucideIcon;
135
+ onSelect: () => void;
136
+ }
137
+
138
+ function CheckboxOrIcon({
139
+ isSelected,
140
+ canSelect,
141
+ Icon,
142
+ onSelect,
143
+ }: CheckboxOrIconProps) {
144
+ if (canSelect) {
145
+ return (
146
+ <>
147
+ <Checkbox
148
+ checked={isSelected}
149
+ onClick={(e) => {
150
+ onSelect();
151
+ e.stopPropagation();
152
+ }}
153
+ className={cn({ "hidden group-hover:flex": !isSelected })}
154
+ />
155
+ <Icon
156
+ size={16}
157
+ className={cn("mr-2", {
158
+ hidden: isSelected,
159
+ "group-hover:hidden": !isSelected,
160
+ })}
161
+ />
162
+ </>
163
+ );
164
+ }
165
+ return <Icon size={16} className="mr-2" />;
166
+ }
167
+
131
168
  /**
132
169
  * File browser component.
133
170
  *
@@ -145,7 +182,6 @@ export const FileBrowser = ({
145
182
  host,
146
183
  }: FileBrowserProps): JSX.Element | null => {
147
184
  const [path, setPath] = useInternalStateWithSync(initialPath);
148
- const [selectAllLabel, setSelectAllLabel] = useState("Select all");
149
185
  const [isUpdatingPath, setIsUpdatingPath] = useState(false);
150
186
  const [showLoadingOverlay, setShowLoadingOverlay] = useState(false);
151
187
 
@@ -158,7 +194,6 @@ export const FileBrowser = ({
158
194
  const { data, error, isPending } = useAsyncData(() => {
159
195
  return list_directory({ path: path });
160
196
  }, [path, randomId]);
161
- const spinnerLabel = "Listing files...";
162
197
 
163
198
  useEffect(() => {
164
199
  if (!isPending) {
@@ -175,25 +210,29 @@ export const FileBrowser = ({
175
210
  };
176
211
  }, [isPending]);
177
212
 
213
+ const files = data?.files ?? [];
214
+ const selectedPaths = new Set(value.map((x) => x.path));
215
+ const canSelectDirectories =
216
+ selectionMode === "directory" || selectionMode === "all";
217
+ const canSelectFiles = selectionMode === "file" || selectionMode === "all";
218
+
219
+ const selectable = files.filter(
220
+ (f) =>
221
+ (canSelectDirectories && f.is_directory) ||
222
+ (canSelectFiles && !f.is_directory),
223
+ );
224
+ const allSelected =
225
+ selectable.length > 0 && selectable.every((f) => selectedPaths.has(f.path));
226
+
178
227
  if (!data && error) {
179
228
  return <Banner kind="danger">{error.message}</Banner>;
180
229
  }
181
230
 
182
- let { files } = data || {};
183
- if (files === undefined) {
184
- files = [];
185
- }
186
-
187
231
  const pathBuilder = PathBuilder.guessDeliminator(initialPath);
188
232
  const delimiter = pathBuilder.deliminator;
189
233
 
190
- const selectedPaths = new Set(value.map((x) => x.path));
191
234
  const selectedFiles = value.map((x) => <li key={x.id}>{x.path}</li>);
192
235
 
193
- const canSelectDirectories =
194
- selectionMode === "directory" || selectionMode === "all";
195
- const canSelectFiles = selectionMode === "file" || selectionMode === "all";
196
-
197
236
  function setNewPath(newPath: string) {
198
237
  // Prevent updating path while updating
199
238
  if (isUpdatingPath) {
@@ -230,9 +269,7 @@ export const FileBrowser = ({
230
269
  return;
231
270
  }
232
271
 
233
- // Update path and reset select all label
234
272
  setPath(newPath);
235
- setSelectAllLabel("Select all");
236
273
  setIsUpdatingPath(false);
237
274
  }
238
275
 
@@ -264,28 +301,18 @@ export const FileBrowser = ({
264
301
  }) {
265
302
  const fileInfo = createFileInfo({ path, name, isDirectory });
266
303
 
267
- if (multiple) {
268
- if (selectedPaths.has(path)) {
269
- setValue(value.filter((x) => x.path !== path));
270
- setSelectAllLabel("Select all");
271
- } else {
272
- setValue([...value, fileInfo]);
273
- }
304
+ if (selectedPaths.has(path)) {
305
+ setValue(value.filter((x) => x.path !== path));
274
306
  } else {
275
- setValue([fileInfo]);
307
+ setValue(multiple ? [...value, fileInfo] : [fileInfo]);
276
308
  }
277
309
  }
278
310
 
279
311
  function deselectAllFiles() {
280
312
  setValue(value.filter((x) => Paths.dirname(x.path) !== path));
281
- setSelectAllLabel("Select all");
282
313
  }
283
314
 
284
315
  function selectAllFiles() {
285
- if (!files) {
286
- return;
287
- }
288
-
289
316
  const filesInView: FileInfo[] = [];
290
317
 
291
318
  for (const file of files) {
@@ -304,7 +331,6 @@ export const FileBrowser = ({
304
331
  }
305
332
 
306
333
  setValue([...value, ...filesInView]);
307
- setSelectAllLabel("Deselect all");
308
334
  }
309
335
 
310
336
  // Create rows for directories and files
@@ -313,7 +339,7 @@ export const FileBrowser = ({
313
339
  // Parent directory ".." row button
314
340
  fileRows.push(
315
341
  <TableRow
316
- className="hover:bg-primary hover:bg-opacity-25 select-none"
342
+ className="hover:bg-accent select-none"
317
343
  key={"Parent directory"}
318
344
  onClick={() => setNewPath(PARENT_DIRECTORY)}
319
345
  >
@@ -344,50 +370,13 @@ export const FileBrowser = ({
344
370
  const Icon = FILE_TYPE_ICONS[fileType];
345
371
 
346
372
  const isSelected = selectedPaths.has(filePath);
347
- const renderCheckboxOrIcon = () => {
348
- if (
349
- (canSelectDirectories && file.is_directory) ||
350
- (canSelectFiles && !file.is_directory)
351
- ) {
352
- return (
353
- <>
354
- <Checkbox
355
- checked={isSelected}
356
- onClick={(e) => {
357
- handleSelection({
358
- path: filePath,
359
- name: file.name,
360
- isDirectory: file.is_directory,
361
- });
362
- e.stopPropagation();
363
- }}
364
- className={cn("", {
365
- "hidden group-hover:flex": !isSelected,
366
- })}
367
- />
368
- <Icon
369
- size={16}
370
- className={cn("mr-2", {
371
- hidden: isSelected,
372
- "group-hover:hidden": !isSelected,
373
- })}
374
- />
375
- </>
376
- );
377
- }
378
-
379
- return <Icon size={16} className="mr-2" />;
380
- };
381
373
 
382
374
  fileRows.push(
383
375
  <TableRow
384
376
  key={file.id}
385
- className={cn(
386
- "hover:bg-primary hover:bg-opacity-25 group select-none",
387
- {
388
- "bg-primary bg-opacity-25": isSelected,
389
- },
390
- )}
377
+ className={cn("hover:bg-accent group select-none", {
378
+ "bg-primary/25 hover:bg-primary/35": isSelected,
379
+ })}
391
380
  onClick={() =>
392
381
  handleClick({
393
382
  path: filePath,
@@ -397,7 +386,21 @@ export const FileBrowser = ({
397
386
  }
398
387
  >
399
388
  <TableCell className="w-[50px] pl-4">
400
- {renderCheckboxOrIcon()}
389
+ <CheckboxOrIcon
390
+ isSelected={isSelected}
391
+ canSelect={
392
+ (canSelectDirectories && file.is_directory) ||
393
+ (canSelectFiles && !file.is_directory)
394
+ }
395
+ Icon={Icon}
396
+ onSelect={() =>
397
+ handleSelection({
398
+ path: filePath,
399
+ name: file.name,
400
+ isDirectory: file.is_directory,
401
+ })
402
+ }
403
+ />
401
404
  </TableCell>
402
405
  <TableCell>{file.name}</TableCell>
403
406
  </TableRow>,
@@ -423,8 +426,9 @@ export const FileBrowser = ({
423
426
  : PluralWords.of("file");
424
427
 
425
428
  const renderHeader = () => {
426
- label = label ?? `Select ${selectionKindLabel.join(" and ", 2)}...`;
427
- const labelText = <Label>{renderHTML({ html: label })}</Label>;
429
+ const displayLabel =
430
+ label ?? `Select ${selectionKindLabel.join(" and ", 2)}...`;
431
+ const labelText = <Label>{renderHTML({ html: displayLabel })}</Label>;
428
432
 
429
433
  if (multiple) {
430
434
  return (
@@ -434,13 +438,9 @@ export const FileBrowser = ({
434
438
  <Button
435
439
  size="xs"
436
440
  variant="link"
437
- onClick={
438
- selectAllLabel === "Select all"
439
- ? () => selectAllFiles()
440
- : () => deselectAllFiles()
441
- }
441
+ onClick={allSelected ? deselectAllFiles : selectAllFiles}
442
442
  >
443
- {renderHTML({ html: selectAllLabel })}
443
+ {allSelected ? "Deselect all" : "Select all"}
444
444
  </Button>
445
445
  </div>
446
446
  </div>
@@ -461,7 +461,7 @@ export const FileBrowser = ({
461
461
  onChange={(e) => setNewPath(e.target.value)}
462
462
  >
463
463
  {parentDirectories.map((dir) => (
464
- <option value={dir} key={dir} selected={dir === path}>
464
+ <option value={dir} key={dir}>
465
465
  {dir}
466
466
  </option>
467
467
  ))}
@@ -487,7 +487,7 @@ export const FileBrowser = ({
487
487
  role="status"
488
488
  >
489
489
  <Spinner size="small" />
490
- <span>{spinnerLabel}</span>
490
+ <span>Listing files...</span>
491
491
  </div>
492
492
  )}
493
493
  <Table className="cursor-pointer table-fixed">