@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.
- package/dist/{constants-DWBOe162.js → constants-D_G8vnDk.js} +5 -4
- package/dist/{formats-7RSCCoSI.js → formats-4m4HuHTj.js} +1 -1
- package/dist/{glide-data-editor-D-Ia_Jsv.js → glide-data-editor-DXF8E-QD.js} +2 -2
- package/dist/main.js +32 -29
- package/dist/{types-Dunk85GC.js → types-DclGb0Yh.js} +1 -1
- package/dist/{vega-component-kU4hFYYJ.js → vega-component-HUc7bIGs.js} +2 -2
- package/package.json +1 -1
- package/src/components/app-config/user-config-form.tsx +14 -1
- package/src/components/editor/chrome/components/contribute-snippet-button.tsx +22 -103
- package/src/components/editor/controls/duplicate-shortcut-banner.tsx +50 -0
- package/src/components/editor/controls/keyboard-shortcuts.tsx +25 -2
- package/src/components/editor/notebook-cell.tsx +4 -3
- package/src/components/editor/output/__tests__/ansi-reduce.test.ts +6 -6
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +3 -3
- package/src/components/pages/home-page.tsx +6 -0
- package/src/components/scratchpad/scratchpad.tsx +2 -1
- package/src/core/constants.ts +10 -0
- package/src/core/layout/useTogglePresenting.ts +69 -25
- package/src/core/state/__mocks__/mocks.ts +1 -0
- package/src/hooks/__tests__/useDuplicateShortcuts.test.ts +449 -0
- package/src/hooks/useDuplicateShortcuts.ts +145 -0
- package/src/plugins/impl/NumberPlugin.tsx +1 -1
- package/src/plugins/impl/__tests__/NumberPlugin.test.tsx +1 -1
- package/src/plugins/layout/NavigationMenuPlugin.tsx +24 -22
- 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,28 +113,30 @@ const NavMenuComponent = ({
|
|
|
113
113
|
const renderMenuItem = (item: MenuItem | MenuItemGroup) => {
|
|
114
114
|
if ("items" in item) {
|
|
115
115
|
return orientation === "horizontal" ? (
|
|
116
|
-
<
|
|
117
|
-
<
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
<
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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.
|
|
116
|
+
expect(jsonToTSV([{ a: 3.14, b: 2.123_45 }], locale)).toEqual(
|
|
117
117
|
"a\tb\n3,14\t2,12345",
|
|
118
118
|
);
|
|
119
119
|
});
|