@marimo-team/islands 0.18.2 → 0.18.3

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 (25) hide show
  1. package/dist/{constants-DWBOe162.js → constants-D_G8vnDk.js} +5 -4
  2. package/dist/{formats-7RSCCoSI.js → formats-4m4HuHTj.js} +1 -1
  3. package/dist/{glide-data-editor-D-Ia_Jsv.js → glide-data-editor-DXF8E-QD.js} +2 -2
  4. package/dist/main.js +32 -29
  5. package/dist/{types-Dunk85GC.js → types-DclGb0Yh.js} +1 -1
  6. package/dist/{vega-component-kU4hFYYJ.js → vega-component-HUc7bIGs.js} +2 -2
  7. package/package.json +1 -1
  8. package/src/components/app-config/user-config-form.tsx +14 -1
  9. package/src/components/editor/chrome/components/contribute-snippet-button.tsx +22 -103
  10. package/src/components/editor/controls/duplicate-shortcut-banner.tsx +50 -0
  11. package/src/components/editor/controls/keyboard-shortcuts.tsx +25 -2
  12. package/src/components/editor/notebook-cell.tsx +4 -3
  13. package/src/components/editor/output/__tests__/ansi-reduce.test.ts +6 -6
  14. package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +3 -3
  15. package/src/components/pages/home-page.tsx +6 -0
  16. package/src/components/scratchpad/scratchpad.tsx +2 -1
  17. package/src/core/constants.ts +10 -0
  18. package/src/core/layout/useTogglePresenting.ts +69 -25
  19. package/src/core/state/__mocks__/mocks.ts +1 -0
  20. package/src/hooks/__tests__/useDuplicateShortcuts.test.ts +449 -0
  21. package/src/hooks/useDuplicateShortcuts.ts +145 -0
  22. package/src/plugins/impl/NumberPlugin.tsx +1 -1
  23. package/src/plugins/impl/__tests__/NumberPlugin.test.tsx +1 -1
  24. package/src/plugins/layout/NavigationMenuPlugin.tsx +24 -22
  25. package/src/utils/__tests__/json-parser.test.ts +1 -1
@@ -0,0 +1,145 @@
1
+ /* Copyright 2024 Marimo. All rights reserved. */
2
+
3
+ import { useMemo } from "react";
4
+ import type {
5
+ HotkeyAction,
6
+ HotkeyGroup,
7
+ HotkeyProvider,
8
+ } from "@/core/hotkeys/hotkeys";
9
+
10
+ export interface DuplicateGroup {
11
+ key: string;
12
+ actions: { action: HotkeyAction; name: string }[];
13
+ }
14
+
15
+ export interface DuplicateShortcutsResult {
16
+ /** All groups of duplicate shortcuts */
17
+ duplicates: DuplicateGroup[];
18
+ /** Check if a specific action has duplicate shortcuts */
19
+ hasDuplicate: (action: HotkeyAction) => boolean;
20
+ /** Get all actions that share the same shortcut as the given action */
21
+ getDuplicatesFor: (action: HotkeyAction) => HotkeyAction[];
22
+ }
23
+
24
+ /**
25
+ * Normalizes a keyboard shortcut key for comparison.
26
+ * - Converts to lowercase
27
+ * - Replaces + with - for consistent comparison
28
+ * - Trims whitespace
29
+ */
30
+ export function normalizeShortcutKey(key: string): string {
31
+ return key.toLowerCase().replaceAll("+", "-").trim();
32
+ }
33
+
34
+ /**
35
+ * Detects duplicate keyboard shortcuts in a hotkey provider.
36
+ * Returns information about which shortcuts are duplicated and provides utilities
37
+ * to check if specific actions have duplicates.
38
+ *
39
+ * This is a pure function that can be tested independently of React.
40
+ *
41
+ * @param hotkeys - The hotkey provider to check for duplicates
42
+ * @param ignoreGroup - Optional group to exclude from duplicate detection (e.g., "Markdown")
43
+ */
44
+ export function findDuplicateShortcuts(
45
+ hotkeys: HotkeyProvider,
46
+ ignoreGroup?: HotkeyGroup,
47
+ ): DuplicateShortcutsResult {
48
+ // Get all groups to check for ignored actions
49
+ const groups = hotkeys.getHotkeyGroups();
50
+ const ignoredActions = ignoreGroup
51
+ ? new Set(groups[ignoreGroup] || [])
52
+ : new Set();
53
+
54
+ // Group actions by their key binding
55
+ const keyMap = new Map<string, { action: HotkeyAction; name: string }[]>();
56
+
57
+ for (const action of hotkeys.iterate()) {
58
+ // Skip actions in ignored groups
59
+ if (ignoredActions.has(action)) {
60
+ continue;
61
+ }
62
+
63
+ const hotkey = hotkeys.getHotkey(action);
64
+
65
+ // Skip empty keys (not set)
66
+ if (!hotkey.key || hotkey.key.trim() === "") {
67
+ continue;
68
+ }
69
+
70
+ const normalizedKey = normalizeShortcutKey(hotkey.key);
71
+
72
+ if (!keyMap.has(normalizedKey)) {
73
+ keyMap.set(normalizedKey, []);
74
+ }
75
+
76
+ const existing = keyMap.get(normalizedKey);
77
+ if (existing) {
78
+ existing.push({
79
+ action,
80
+ name: hotkey.name,
81
+ });
82
+ }
83
+ }
84
+
85
+ // Filter to only groups with duplicates (more than one action per key)
86
+ const duplicates: DuplicateGroup[] = [];
87
+ const duplicateActionSet = new Set<HotkeyAction>();
88
+
89
+ for (const [key, actions] of keyMap.entries()) {
90
+ if (actions.length > 1) {
91
+ duplicates.push({ key, actions });
92
+ for (const { action } of actions) {
93
+ duplicateActionSet.add(action);
94
+ }
95
+ }
96
+ }
97
+
98
+ // Helper to check if an action has duplicates
99
+ const hasDuplicate = (action: HotkeyAction): boolean => {
100
+ return duplicateActionSet.has(action);
101
+ };
102
+
103
+ // Helper to get all duplicates for a specific action
104
+ const getDuplicatesFor = (action: HotkeyAction): HotkeyAction[] => {
105
+ const hotkey = hotkeys.getHotkey(action);
106
+ if (!hotkey.key || hotkey.key.trim() === "") {
107
+ return [];
108
+ }
109
+
110
+ const normalizedKey = normalizeShortcutKey(hotkey.key);
111
+
112
+ const group = duplicates.find((d) => d.key === normalizedKey);
113
+ if (!group || group.actions.length <= 1) {
114
+ return [];
115
+ }
116
+
117
+ return group.actions
118
+ .filter((a) => a.action !== action)
119
+ .map((a) => a.action);
120
+ };
121
+
122
+ return {
123
+ duplicates,
124
+ hasDuplicate,
125
+ getDuplicatesFor,
126
+ };
127
+ }
128
+
129
+ /**
130
+ * Hook to detect duplicate keyboard shortcuts.
131
+ * Returns information about which shortcuts are duplicated and provides utilities
132
+ * to check if specific actions have duplicates.
133
+ *
134
+ * @param hotkeys - The hotkey provider to check for duplicates
135
+ * @param ignoreGroup - Optional group to exclude from duplicate detection (e.g., "Markdown")
136
+ */
137
+ export function useDuplicateShortcuts(
138
+ hotkeys: HotkeyProvider,
139
+ ignoreGroup?: HotkeyGroup,
140
+ ): DuplicateShortcutsResult {
141
+ return useMemo(
142
+ () => findDuplicateShortcuts(hotkeys, ignoreGroup),
143
+ [hotkeys, ignoreGroup],
144
+ );
145
+ }
@@ -79,7 +79,7 @@ const NumberComponent = (props: NumberComponentProps): JSX.Element => {
79
79
  // This needs to be `?? NaN` since `?? undefined` makes uncontrolled component
80
80
  // and can lead to leaving the old value in forms (https://github.com/marimo-team/marimo/issues/7352)
81
81
  // We out NaNs later
82
- value={value ?? NaN}
82
+ value={value ?? Number.NaN}
83
83
  step={props.step}
84
84
  onChange={handleChange}
85
85
  id={id}
@@ -113,7 +113,7 @@ describe("NumberPlugin", () => {
113
113
  z.infer<(typeof plugin)["validator"]>
114
114
  > = {
115
115
  host,
116
- value: NaN,
116
+ value: Number.NaN,
117
117
  setValue,
118
118
  data: {
119
119
  start: 0,
@@ -113,28 +113,30 @@ const NavMenuComponent = ({
113
113
  const renderMenuItem = (item: MenuItem | MenuItemGroup) => {
114
114
  if ("items" in item) {
115
115
  return orientation === "horizontal" ? (
116
- <NavigationMenuItem key={item.label}>
117
- <NavigationMenuTrigger>
118
- {renderHTML({ html: item.label })}
119
- </NavigationMenuTrigger>
120
- <NavigationMenuContent>
121
- <NavigationMenuList>
122
- <ul className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px] ">
123
- {item.items.map((subItem) => (
124
- <ListItem
125
- key={subItem.label}
126
- label={subItem.label}
127
- href={preserveQueryParams(subItem.href)}
128
- target={target(subItem.href)}
129
- >
130
- {subItem.description &&
131
- renderHTML({ html: subItem.description })}
132
- </ListItem>
133
- ))}
134
- </ul>
135
- </NavigationMenuList>
136
- </NavigationMenuContent>
137
- </NavigationMenuItem>
116
+ <NavigationMenu orientation="horizontal" key={item.label}>
117
+ <NavigationMenuList>
118
+ <NavigationMenuItem>
119
+ <NavigationMenuTrigger>
120
+ {renderHTML({ html: item.label })}
121
+ </NavigationMenuTrigger>
122
+ <NavigationMenuContent>
123
+ <ul className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px] ">
124
+ {item.items.map((subItem) => (
125
+ <ListItem
126
+ key={subItem.label}
127
+ label={subItem.label}
128
+ href={preserveQueryParams(subItem.href)}
129
+ target={target(subItem.href)}
130
+ >
131
+ {subItem.description &&
132
+ renderHTML({ html: subItem.description })}
133
+ </ListItem>
134
+ ))}
135
+ </ul>
136
+ </NavigationMenuContent>
137
+ </NavigationMenuItem>
138
+ </NavigationMenuList>
139
+ </NavigationMenu>
138
140
  ) : (
139
141
  <NavigationMenuItem key={item.label}>
140
142
  <div
@@ -113,7 +113,7 @@ it("can convert json to tsv with fr-FR locale", () => {
113
113
  const locale = "fr-FR";
114
114
 
115
115
  // Handles floats with fr-FR locale (uses , as decimal separator)
116
- expect(jsonToTSV([{ a: 3.14, b: 2.12345 }], locale)).toEqual(
116
+ expect(jsonToTSV([{ a: 3.14, b: 2.123_45 }], locale)).toEqual(
117
117
  "a\tb\n3,14\t2,12345",
118
118
  );
119
119
  });