roamjs-components 0.86.0-alpha → 0.86.0-alpha.2
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/package.json +18 -10
- package/patches/@blueprintjs+core+3.50.4.patch +48 -0
- package/patches/@blueprintjs+select+3.18.6.patch +12 -0
- package/src/components/AutocompleteInput.tsx +248 -248
- package/src/components/BlockErrorBoundary.tsx +5 -1
- package/src/components/BlockInput.tsx +117 -117
- package/src/components/ComponentContainer.tsx +70 -69
- package/src/components/ConfigPage.tsx +320 -320
- package/src/components/ConfigPanels/BlocksPanel.tsx +100 -100
- package/src/components/ConfigPanels/MultiChildPanel.tsx +99 -99
- package/src/components/ConfigPanels/OauthPanel.tsx +127 -127
- package/src/components/ConfigPanels/useSingleChildValue.tsx +63 -63
- package/src/components/CursorMenu.tsx +1 -1
- package/src/components/ExternalLogin.tsx +190 -190
- package/src/components/FormDialog.tsx +503 -503
- package/src/components/MenuItemSelect.tsx +7 -2
- package/src/components/OauthSelect.tsx +40 -40
- package/src/components/PageInput.tsx +17 -17
- package/src/components.tsx +43 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roamjs-components",
|
|
3
3
|
"description": "Expansive toolset, utilities, & components for developing RoamJS extensions.",
|
|
4
|
-
"version": "0.86.0-alpha",
|
|
4
|
+
"version": "0.86.0-alpha.2",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"scripts": {
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"@types/jsdom": "^20.0.1",
|
|
27
27
|
"@types/marked": "^4.0.3",
|
|
28
28
|
"@types/nanoid": "2.0.0",
|
|
29
|
-
"@types/react": "
|
|
30
|
-
"@types/react-dom": "
|
|
29
|
+
"@types/react": "18.2.0",
|
|
30
|
+
"@types/react-dom": "18.2.0",
|
|
31
31
|
"@types/use-sync-external-store": "^0.0.3",
|
|
32
32
|
"chrono-node": "2.3.0",
|
|
33
33
|
"crypto-js": "3.1.9-1",
|
|
@@ -40,14 +40,13 @@
|
|
|
40
40
|
"marked": "4.0.16",
|
|
41
41
|
"marked-react": "1.1.2",
|
|
42
42
|
"nanoid": "2.0.4",
|
|
43
|
-
"react": "
|
|
44
|
-
"react-dom": "
|
|
43
|
+
"react": "18.2.0",
|
|
44
|
+
"react-dom": "18.2.0",
|
|
45
45
|
"tslib": "2.2.0",
|
|
46
46
|
"use-sync-external-store": "^1.2.0"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@samepage/scripts": "^0.74.2",
|
|
50
|
-
"aws-sdk-plus": "^0.5.3",
|
|
51
50
|
"color": "^4.0.1",
|
|
52
51
|
"date-fns": "^2.27.0",
|
|
53
52
|
"edn-data": "^1.0.0",
|
|
@@ -68,7 +67,8 @@
|
|
|
68
67
|
"http-server": "^14.1.1",
|
|
69
68
|
"prettier": "^2.3.1",
|
|
70
69
|
"tslint-config-prettier": "^1.18.0",
|
|
71
|
-
"tslint-react-hooks": "^2.2.2"
|
|
70
|
+
"tslint-react-hooks": "^2.2.2",
|
|
71
|
+
"dotenv": "16.3.1"
|
|
72
72
|
},
|
|
73
73
|
"engines": {
|
|
74
74
|
"npm": ">=7.0.0",
|
|
@@ -78,9 +78,17 @@
|
|
|
78
78
|
"roamjs": "./scripts/index.js"
|
|
79
79
|
},
|
|
80
80
|
"overrides": {
|
|
81
|
-
"@
|
|
82
|
-
"react": "
|
|
83
|
-
"react-dom": "
|
|
81
|
+
"@blueprintjs/core": {
|
|
82
|
+
"react": "18.2.0",
|
|
83
|
+
"react-dom": "18.2.0"
|
|
84
|
+
},
|
|
85
|
+
"@blueprintjs/datetime": {
|
|
86
|
+
"react": "18.2.0",
|
|
87
|
+
"react-dom": "18.2.0"
|
|
88
|
+
},
|
|
89
|
+
"@blueprintjs/select": {
|
|
90
|
+
"react": "18.2.0",
|
|
91
|
+
"react-dom": "18.2.0"
|
|
84
92
|
}
|
|
85
93
|
},
|
|
86
94
|
"samepage": {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
diff --git a/node_modules/@blueprintjs/core/lib/esm/components/alert/alert.d.ts b/node_modules/@blueprintjs/core/lib/esm/components/alert/alert.d.ts
|
|
2
|
+
index 09b06be..f74f6a3 100644
|
|
3
|
+
--- a/node_modules/@blueprintjs/core/lib/esm/components/alert/alert.d.ts
|
|
4
|
+
+++ b/node_modules/@blueprintjs/core/lib/esm/components/alert/alert.d.ts
|
|
5
|
+
@@ -5,6 +5,7 @@ import { IOverlayLifecycleProps } from "../overlay/overlay";
|
|
6
|
+
export declare type AlertProps = IAlertProps;
|
|
7
|
+
/** @deprecated use AlertProps */
|
|
8
|
+
export interface IAlertProps extends IOverlayLifecycleProps, Props {
|
|
9
|
+
+ children?: React.ReactNode;
|
|
10
|
+
/**
|
|
11
|
+
* Whether pressing <kbd>escape</kbd> when focused on the Alert should cancel the alert.
|
|
12
|
+
* If this prop is enabled, then either `onCancel` or `onClose` must also be defined.
|
|
13
|
+
diff --git a/node_modules/@blueprintjs/core/lib/esm/components/dialog/dialog.d.ts b/node_modules/@blueprintjs/core/lib/esm/components/dialog/dialog.d.ts
|
|
14
|
+
index e90ee31..3e4f4ef 100644
|
|
15
|
+
--- a/node_modules/@blueprintjs/core/lib/esm/components/dialog/dialog.d.ts
|
|
16
|
+
+++ b/node_modules/@blueprintjs/core/lib/esm/components/dialog/dialog.d.ts
|
|
17
|
+
@@ -6,6 +6,7 @@ import { IBackdropProps, OverlayableProps } from "../overlay/overlay";
|
|
18
|
+
export declare type DialogProps = IDialogProps;
|
|
19
|
+
/** @deprecated use DialogProps */
|
|
20
|
+
export interface IDialogProps extends OverlayableProps, IBackdropProps, Props {
|
|
21
|
+
+ children?: React.ReactNode;
|
|
22
|
+
/**
|
|
23
|
+
* Toggles the visibility of the overlay and its children.
|
|
24
|
+
* This prop is required because the component is controlled.
|
|
25
|
+
diff --git a/node_modules/@blueprintjs/core/lib/esm/components/tabs/tabs.d.ts b/node_modules/@blueprintjs/core/lib/esm/components/tabs/tabs.d.ts
|
|
26
|
+
index 2ca5fe5..870a32e 100644
|
|
27
|
+
--- a/node_modules/@blueprintjs/core/lib/esm/components/tabs/tabs.d.ts
|
|
28
|
+
+++ b/node_modules/@blueprintjs/core/lib/esm/components/tabs/tabs.d.ts
|
|
29
|
+
@@ -6,6 +6,7 @@ export declare const Expander: React.FunctionComponent;
|
|
30
|
+
export declare type TabsProps = ITabsProps;
|
|
31
|
+
/** @deprecated use TabsProps */
|
|
32
|
+
export interface ITabsProps extends Props {
|
|
33
|
+
+ children?: React.ReactNode;
|
|
34
|
+
/**
|
|
35
|
+
* Whether the selected tab indicator should animate its movement.
|
|
36
|
+
*
|
|
37
|
+
diff --git a/node_modules/@blueprintjs/core/lib/esm/components/tooltip/tooltip.d.ts b/node_modules/@blueprintjs/core/lib/esm/components/tooltip/tooltip.d.ts
|
|
38
|
+
index 94f4af9..4db48a9 100644
|
|
39
|
+
--- a/node_modules/@blueprintjs/core/lib/esm/components/tooltip/tooltip.d.ts
|
|
40
|
+
+++ b/node_modules/@blueprintjs/core/lib/esm/components/tooltip/tooltip.d.ts
|
|
41
|
+
@@ -6,6 +6,7 @@ import { IPopoverSharedProps } from "../popover/popoverSharedProps";
|
|
42
|
+
export declare type TooltipProps = ITooltipProps;
|
|
43
|
+
/** @deprecated use TooltipProps */
|
|
44
|
+
export interface ITooltipProps extends IPopoverSharedProps, IntentProps {
|
|
45
|
+
+ children?: React.ReactNode;
|
|
46
|
+
/**
|
|
47
|
+
* The content that will be displayed inside of the tooltip.
|
|
48
|
+
*/
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
diff --git a/node_modules/@blueprintjs/select/lib/esm/components/select/select.d.ts b/node_modules/@blueprintjs/select/lib/esm/components/select/select.d.ts
|
|
2
|
+
index f8b9bc6..ee0b6da 100644
|
|
3
|
+
--- a/node_modules/@blueprintjs/select/lib/esm/components/select/select.d.ts
|
|
4
|
+
+++ b/node_modules/@blueprintjs/select/lib/esm/components/select/select.d.ts
|
|
5
|
+
@@ -4,6 +4,7 @@ import { IListItemsProps } from "../../common";
|
|
6
|
+
export declare type SelectProps<T> = ISelectProps<T>;
|
|
7
|
+
/** @deprecated use SelectProps */
|
|
8
|
+
export interface ISelectProps<T> extends IListItemsProps<T> {
|
|
9
|
+
+ children?: React.ReactNode;
|
|
10
|
+
/**
|
|
11
|
+
* Whether the component should take up the full width of its container.
|
|
12
|
+
* This overrides `popoverProps.fill`. You also have to ensure that the child
|
|
@@ -1,248 +1,248 @@
|
|
|
1
|
-
import {
|
|
2
|
-
InputGroup,
|
|
3
|
-
Menu,
|
|
4
|
-
MenuItem,
|
|
5
|
-
PopoverPosition,
|
|
6
|
-
Popover,
|
|
7
|
-
Button,
|
|
8
|
-
TextArea,
|
|
9
|
-
} from "@blueprintjs/core";
|
|
10
|
-
import React, {
|
|
11
|
-
useState,
|
|
12
|
-
useCallback,
|
|
13
|
-
useMemo,
|
|
14
|
-
useRef,
|
|
15
|
-
useEffect,
|
|
16
|
-
} from "react";
|
|
17
|
-
import useArrowKeyDown from "../hooks/useArrowKeyDown";
|
|
18
|
-
import fuzzy from "fuzzy";
|
|
19
|
-
|
|
20
|
-
type FilterOptions<T> = (options: T[], query: string) => T[];
|
|
21
|
-
type OnNewItem<T> = (s: string) => T;
|
|
22
|
-
type ItemToQuery<T> = (t?: T) => string;
|
|
23
|
-
|
|
24
|
-
export type AutocompleteInputProps<T = string> = {
|
|
25
|
-
value?: T;
|
|
26
|
-
setValue: (q: T) => void;
|
|
27
|
-
showButton?: boolean;
|
|
28
|
-
onBlur?: (v: string) => void;
|
|
29
|
-
onConfirm?: () => void;
|
|
30
|
-
options?: T[];
|
|
31
|
-
placeholder?: string;
|
|
32
|
-
autoFocus?: boolean;
|
|
33
|
-
multiline?: boolean;
|
|
34
|
-
id?: string;
|
|
35
|
-
filterOptions?: FilterOptions<T>;
|
|
36
|
-
itemToQuery?: ItemToQuery<T>;
|
|
37
|
-
renderItem?: (props: {
|
|
38
|
-
item: T;
|
|
39
|
-
onClick: () => void;
|
|
40
|
-
active: boolean;
|
|
41
|
-
}) => React.ReactElement;
|
|
42
|
-
onNewItem?: OnNewItem<T>;
|
|
43
|
-
disabled?: boolean;
|
|
44
|
-
maxItemsDisplayed?: number;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint
|
|
48
|
-
const AutocompleteInput = <T extends unknown = string>({
|
|
49
|
-
value,
|
|
50
|
-
setValue,
|
|
51
|
-
onBlur,
|
|
52
|
-
onConfirm,
|
|
53
|
-
showButton,
|
|
54
|
-
options = [],
|
|
55
|
-
placeholder = "Enter value",
|
|
56
|
-
autoFocus,
|
|
57
|
-
multiline,
|
|
58
|
-
id,
|
|
59
|
-
filterOptions: _filterOptions,
|
|
60
|
-
itemToQuery: _itemToQuery,
|
|
61
|
-
renderItem,
|
|
62
|
-
onNewItem: _onNewItem,
|
|
63
|
-
disabled,
|
|
64
|
-
maxItemsDisplayed = Infinity,
|
|
65
|
-
}: AutocompleteInputProps<T>): React.ReactElement => {
|
|
66
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
67
|
-
const itemToQuery = useMemo<ItemToQuery<T>>(
|
|
68
|
-
() => _itemToQuery || ((s) => (s ? `${s}` : "")),
|
|
69
|
-
[_itemToQuery]
|
|
70
|
-
);
|
|
71
|
-
const [query, setQuery] = useState<string>(() => itemToQuery(value));
|
|
72
|
-
const open = useCallback(() => setIsOpen(true), [setIsOpen]);
|
|
73
|
-
const close = useCallback(() => setIsOpen(false), [setIsOpen]);
|
|
74
|
-
const [isTyping, setIsTyping] = useState(false);
|
|
75
|
-
const filterOptions = useMemo<FilterOptions<T>>(
|
|
76
|
-
() =>
|
|
77
|
-
_filterOptions ||
|
|
78
|
-
((o, q) =>
|
|
79
|
-
fuzzy
|
|
80
|
-
.filter(q, o, { extract: itemToQuery })
|
|
81
|
-
.map((f) => f.original)
|
|
82
|
-
.filter((f): f is T => !!f)),
|
|
83
|
-
[_filterOptions, itemToQuery]
|
|
84
|
-
);
|
|
85
|
-
const onNewItem = useMemo<OnNewItem<T>>(
|
|
86
|
-
() => _onNewItem || ((s) => s as T),
|
|
87
|
-
[_onNewItem]
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
const items = useMemo(
|
|
91
|
-
() =>
|
|
92
|
-
(query ? filterOptions(options, query) : options).slice(
|
|
93
|
-
0,
|
|
94
|
-
maxItemsDisplayed
|
|
95
|
-
),
|
|
96
|
-
[query, options, filterOptions, maxItemsDisplayed]
|
|
97
|
-
);
|
|
98
|
-
const menuRef = useRef<HTMLUListElement>(null);
|
|
99
|
-
const inputRef = useRef<HTMLInputElement & HTMLTextAreaElement>(null);
|
|
100
|
-
const onEnter = useCallback(
|
|
101
|
-
(value?: T) => {
|
|
102
|
-
if (isOpen && value) {
|
|
103
|
-
setQuery(itemToQuery(value));
|
|
104
|
-
setValue(value);
|
|
105
|
-
setIsTyping(false);
|
|
106
|
-
} else if (onConfirm) {
|
|
107
|
-
onConfirm();
|
|
108
|
-
} else {
|
|
109
|
-
setIsOpen(true);
|
|
110
|
-
}
|
|
111
|
-
},
|
|
112
|
-
[setValue, onConfirm, isOpen]
|
|
113
|
-
);
|
|
114
|
-
const { activeIndex, onKeyDown } = useArrowKeyDown({
|
|
115
|
-
onEnter,
|
|
116
|
-
results: items,
|
|
117
|
-
menuRef,
|
|
118
|
-
});
|
|
119
|
-
useEffect(() => {
|
|
120
|
-
if (!items.length || !isTyping) close();
|
|
121
|
-
else open();
|
|
122
|
-
}, [items, close, open, isTyping]);
|
|
123
|
-
useEffect(() => {
|
|
124
|
-
if (query && isOpen) setValue(items[activeIndex] || onNewItem(query));
|
|
125
|
-
else if (query) setValue(onNewItem(query));
|
|
126
|
-
}, [setValue, activeIndex, items, onNewItem, query]);
|
|
127
|
-
useEffect(() => {
|
|
128
|
-
if (
|
|
129
|
-
inputRef.current &&
|
|
130
|
-
inputRef.current === document.activeElement &&
|
|
131
|
-
value
|
|
132
|
-
) {
|
|
133
|
-
const index = itemToQuery(value).length;
|
|
134
|
-
inputRef.current.setSelectionRange(index, index);
|
|
135
|
-
}
|
|
136
|
-
const touchEndListener = (e: TouchEvent) => {
|
|
137
|
-
if (
|
|
138
|
-
!e.target ||
|
|
139
|
-
!menuRef.current ||
|
|
140
|
-
menuRef.current.contains(e.target as Element)
|
|
141
|
-
) {
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
if (!inputRef.current || inputRef.current.contains(e.target as Element)) {
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
close();
|
|
148
|
-
};
|
|
149
|
-
document.body.addEventListener("touchend", touchEndListener);
|
|
150
|
-
return () =>
|
|
151
|
-
document.body.removeEventListener("touchend", touchEndListener);
|
|
152
|
-
}, [inputRef, menuRef, close]);
|
|
153
|
-
const Input = useMemo(() => (multiline ? TextArea : InputGroup), [multiline]);
|
|
154
|
-
return (
|
|
155
|
-
<Popover
|
|
156
|
-
portalClassName={"roamjs-autocomplete-input"}
|
|
157
|
-
targetClassName={"roamjs-autocomplete-input-target"}
|
|
158
|
-
captureDismiss={true}
|
|
159
|
-
isOpen={isOpen}
|
|
160
|
-
onOpened={open}
|
|
161
|
-
minimal
|
|
162
|
-
autoFocus={false}
|
|
163
|
-
enforceFocus={false}
|
|
164
|
-
position={PopoverPosition.BOTTOM_LEFT}
|
|
165
|
-
modifiers={{
|
|
166
|
-
flip: { enabled: false },
|
|
167
|
-
preventOverflow: { enabled: false },
|
|
168
|
-
}}
|
|
169
|
-
content={
|
|
170
|
-
<Menu className={"max-h-64 overflow-auto max-w-md"} ulRef={menuRef}>
|
|
171
|
-
{items.map((t, i) => {
|
|
172
|
-
const onClick = () => {
|
|
173
|
-
setIsTyping(false);
|
|
174
|
-
setValue(t);
|
|
175
|
-
setQuery(itemToQuery(t));
|
|
176
|
-
inputRef.current?.focus();
|
|
177
|
-
};
|
|
178
|
-
const sharedProps = {
|
|
179
|
-
onClick,
|
|
180
|
-
onTouchEnd: onClick,
|
|
181
|
-
active: activeIndex === i,
|
|
182
|
-
};
|
|
183
|
-
return renderItem ? (
|
|
184
|
-
<React.Fragment key={i}>
|
|
185
|
-
{renderItem?.({
|
|
186
|
-
item: t,
|
|
187
|
-
...sharedProps,
|
|
188
|
-
})}
|
|
189
|
-
</React.Fragment>
|
|
190
|
-
) : (
|
|
191
|
-
<MenuItem
|
|
192
|
-
text={itemToQuery(t)}
|
|
193
|
-
key={i}
|
|
194
|
-
multiline
|
|
195
|
-
{...sharedProps}
|
|
196
|
-
/>
|
|
197
|
-
);
|
|
198
|
-
})}
|
|
199
|
-
</Menu>
|
|
200
|
-
}
|
|
201
|
-
target={
|
|
202
|
-
<Input
|
|
203
|
-
disabled={disabled}
|
|
204
|
-
value={query}
|
|
205
|
-
onChange={(e) => {
|
|
206
|
-
setIsTyping(true);
|
|
207
|
-
setQuery(e.target.value);
|
|
208
|
-
}}
|
|
209
|
-
autoFocus={autoFocus}
|
|
210
|
-
placeholder={placeholder}
|
|
211
|
-
onKeyDown={(e) => {
|
|
212
|
-
if (e.key === "Escape") {
|
|
213
|
-
e.stopPropagation();
|
|
214
|
-
close();
|
|
215
|
-
} else {
|
|
216
|
-
onKeyDown(e);
|
|
217
|
-
}
|
|
218
|
-
}}
|
|
219
|
-
id={id}
|
|
220
|
-
onClick={() => setIsTyping(true)}
|
|
221
|
-
onBlur={(e) => {
|
|
222
|
-
if (
|
|
223
|
-
e.relatedTarget === null ||
|
|
224
|
-
!(e.relatedTarget as HTMLElement).closest?.(
|
|
225
|
-
".roamjs-autocomplete-input"
|
|
226
|
-
)
|
|
227
|
-
) {
|
|
228
|
-
setIsTyping(false);
|
|
229
|
-
}
|
|
230
|
-
if (onBlur) {
|
|
231
|
-
onBlur(e.target.value);
|
|
232
|
-
}
|
|
233
|
-
}}
|
|
234
|
-
inputRef={inputRef}
|
|
235
|
-
{...(showButton
|
|
236
|
-
? {
|
|
237
|
-
rightElement: (
|
|
238
|
-
<Button icon={"add"} minimal onClick={() => onEnter()} />
|
|
239
|
-
),
|
|
240
|
-
}
|
|
241
|
-
: {})}
|
|
242
|
-
/>
|
|
243
|
-
}
|
|
244
|
-
/>
|
|
245
|
-
);
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
export default AutocompleteInput;
|
|
1
|
+
import {
|
|
2
|
+
InputGroup,
|
|
3
|
+
Menu,
|
|
4
|
+
MenuItem,
|
|
5
|
+
PopoverPosition,
|
|
6
|
+
Popover,
|
|
7
|
+
Button,
|
|
8
|
+
TextArea,
|
|
9
|
+
} from "@blueprintjs/core";
|
|
10
|
+
import React, {
|
|
11
|
+
useState,
|
|
12
|
+
useCallback,
|
|
13
|
+
useMemo,
|
|
14
|
+
useRef,
|
|
15
|
+
useEffect,
|
|
16
|
+
} from "react";
|
|
17
|
+
import useArrowKeyDown from "../hooks/useArrowKeyDown";
|
|
18
|
+
import fuzzy from "fuzzy";
|
|
19
|
+
|
|
20
|
+
type FilterOptions<T> = (options: T[], query: string) => T[];
|
|
21
|
+
type OnNewItem<T> = (s: string) => T;
|
|
22
|
+
type ItemToQuery<T> = (t?: T) => string;
|
|
23
|
+
|
|
24
|
+
export type AutocompleteInputProps<T = string> = {
|
|
25
|
+
value?: T;
|
|
26
|
+
setValue: (q: T) => void;
|
|
27
|
+
showButton?: boolean;
|
|
28
|
+
onBlur?: (v: string) => void;
|
|
29
|
+
onConfirm?: () => void;
|
|
30
|
+
options?: T[];
|
|
31
|
+
placeholder?: string;
|
|
32
|
+
autoFocus?: boolean;
|
|
33
|
+
multiline?: boolean;
|
|
34
|
+
id?: string;
|
|
35
|
+
filterOptions?: FilterOptions<T>;
|
|
36
|
+
itemToQuery?: ItemToQuery<T>;
|
|
37
|
+
renderItem?: (props: {
|
|
38
|
+
item: T;
|
|
39
|
+
onClick: () => void;
|
|
40
|
+
active: boolean;
|
|
41
|
+
}) => React.ReactElement;
|
|
42
|
+
onNewItem?: OnNewItem<T>;
|
|
43
|
+
disabled?: boolean;
|
|
44
|
+
maxItemsDisplayed?: number;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint
|
|
48
|
+
const AutocompleteInput = <T extends unknown = string>({
|
|
49
|
+
value,
|
|
50
|
+
setValue,
|
|
51
|
+
onBlur,
|
|
52
|
+
onConfirm,
|
|
53
|
+
showButton,
|
|
54
|
+
options = [],
|
|
55
|
+
placeholder = "Enter value",
|
|
56
|
+
autoFocus,
|
|
57
|
+
multiline,
|
|
58
|
+
id,
|
|
59
|
+
filterOptions: _filterOptions,
|
|
60
|
+
itemToQuery: _itemToQuery,
|
|
61
|
+
renderItem,
|
|
62
|
+
onNewItem: _onNewItem,
|
|
63
|
+
disabled,
|
|
64
|
+
maxItemsDisplayed = Infinity,
|
|
65
|
+
}: AutocompleteInputProps<T>): React.ReactElement => {
|
|
66
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
67
|
+
const itemToQuery = useMemo<ItemToQuery<T>>(
|
|
68
|
+
() => _itemToQuery || ((s) => (s ? `${s}` : "")),
|
|
69
|
+
[_itemToQuery]
|
|
70
|
+
);
|
|
71
|
+
const [query, setQuery] = useState<string>(() => itemToQuery(value));
|
|
72
|
+
const open = useCallback(() => setIsOpen(true), [setIsOpen]);
|
|
73
|
+
const close = useCallback(() => setIsOpen(false), [setIsOpen]);
|
|
74
|
+
const [isTyping, setIsTyping] = useState(false);
|
|
75
|
+
const filterOptions = useMemo<FilterOptions<T>>(
|
|
76
|
+
() =>
|
|
77
|
+
_filterOptions ||
|
|
78
|
+
((o, q) =>
|
|
79
|
+
fuzzy
|
|
80
|
+
.filter(q, o, { extract: itemToQuery })
|
|
81
|
+
.map((f) => f.original)
|
|
82
|
+
.filter((f): f is T => !!f)),
|
|
83
|
+
[_filterOptions, itemToQuery]
|
|
84
|
+
);
|
|
85
|
+
const onNewItem = useMemo<OnNewItem<T>>(
|
|
86
|
+
() => _onNewItem || ((s) => s as T),
|
|
87
|
+
[_onNewItem]
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const items = useMemo(
|
|
91
|
+
() =>
|
|
92
|
+
(query ? filterOptions(options, query) : options).slice(
|
|
93
|
+
0,
|
|
94
|
+
maxItemsDisplayed
|
|
95
|
+
),
|
|
96
|
+
[query, options, filterOptions, maxItemsDisplayed]
|
|
97
|
+
);
|
|
98
|
+
const menuRef = useRef<HTMLUListElement>(null);
|
|
99
|
+
const inputRef = useRef<HTMLInputElement & HTMLTextAreaElement>(null);
|
|
100
|
+
const onEnter = useCallback(
|
|
101
|
+
(value?: T) => {
|
|
102
|
+
if (isOpen && value) {
|
|
103
|
+
setQuery(itemToQuery(value));
|
|
104
|
+
setValue(value);
|
|
105
|
+
setIsTyping(false);
|
|
106
|
+
} else if (onConfirm) {
|
|
107
|
+
onConfirm();
|
|
108
|
+
} else {
|
|
109
|
+
setIsOpen(true);
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
[setValue, onConfirm, isOpen]
|
|
113
|
+
);
|
|
114
|
+
const { activeIndex, onKeyDown } = useArrowKeyDown({
|
|
115
|
+
onEnter,
|
|
116
|
+
results: items,
|
|
117
|
+
menuRef,
|
|
118
|
+
});
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (!items.length || !isTyping) close();
|
|
121
|
+
else open();
|
|
122
|
+
}, [items, close, open, isTyping]);
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
if (query && isOpen) setValue(items[activeIndex] || onNewItem(query));
|
|
125
|
+
else if (query) setValue(onNewItem(query));
|
|
126
|
+
}, [setValue, activeIndex, items, onNewItem, query]);
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
if (
|
|
129
|
+
inputRef.current &&
|
|
130
|
+
inputRef.current === document.activeElement &&
|
|
131
|
+
value
|
|
132
|
+
) {
|
|
133
|
+
const index = itemToQuery(value).length;
|
|
134
|
+
inputRef.current.setSelectionRange(index, index);
|
|
135
|
+
}
|
|
136
|
+
const touchEndListener = (e: TouchEvent) => {
|
|
137
|
+
if (
|
|
138
|
+
!e.target ||
|
|
139
|
+
!menuRef.current ||
|
|
140
|
+
menuRef.current.contains(e.target as Element)
|
|
141
|
+
) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (!inputRef.current || inputRef.current.contains(e.target as Element)) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
close();
|
|
148
|
+
};
|
|
149
|
+
document.body.addEventListener("touchend", touchEndListener);
|
|
150
|
+
return () =>
|
|
151
|
+
document.body.removeEventListener("touchend", touchEndListener);
|
|
152
|
+
}, [inputRef, menuRef, close]);
|
|
153
|
+
const Input = useMemo(() => (multiline ? TextArea : InputGroup), [multiline]);
|
|
154
|
+
return (
|
|
155
|
+
<Popover
|
|
156
|
+
portalClassName={"roamjs-autocomplete-input"}
|
|
157
|
+
targetClassName={"roamjs-autocomplete-input-target"}
|
|
158
|
+
captureDismiss={true}
|
|
159
|
+
isOpen={isOpen}
|
|
160
|
+
onOpened={open}
|
|
161
|
+
minimal
|
|
162
|
+
autoFocus={false}
|
|
163
|
+
enforceFocus={false}
|
|
164
|
+
position={PopoverPosition.BOTTOM_LEFT}
|
|
165
|
+
modifiers={{
|
|
166
|
+
flip: { enabled: false },
|
|
167
|
+
preventOverflow: { enabled: false },
|
|
168
|
+
}}
|
|
169
|
+
content={
|
|
170
|
+
<Menu className={"max-h-64 overflow-auto max-w-md"} ulRef={menuRef}>
|
|
171
|
+
{items.map((t, i) => {
|
|
172
|
+
const onClick = () => {
|
|
173
|
+
setIsTyping(false);
|
|
174
|
+
setValue(t);
|
|
175
|
+
setQuery(itemToQuery(t));
|
|
176
|
+
inputRef.current?.focus();
|
|
177
|
+
};
|
|
178
|
+
const sharedProps = {
|
|
179
|
+
onClick,
|
|
180
|
+
onTouchEnd: onClick,
|
|
181
|
+
active: activeIndex === i,
|
|
182
|
+
};
|
|
183
|
+
return renderItem ? (
|
|
184
|
+
<React.Fragment key={i}>
|
|
185
|
+
{renderItem?.({
|
|
186
|
+
item: t,
|
|
187
|
+
...sharedProps,
|
|
188
|
+
})}
|
|
189
|
+
</React.Fragment>
|
|
190
|
+
) : (
|
|
191
|
+
<MenuItem
|
|
192
|
+
text={itemToQuery(t)}
|
|
193
|
+
key={i}
|
|
194
|
+
multiline
|
|
195
|
+
{...sharedProps}
|
|
196
|
+
/>
|
|
197
|
+
);
|
|
198
|
+
})}
|
|
199
|
+
</Menu>
|
|
200
|
+
}
|
|
201
|
+
target={
|
|
202
|
+
<Input
|
|
203
|
+
disabled={disabled}
|
|
204
|
+
value={query}
|
|
205
|
+
onChange={(e) => {
|
|
206
|
+
setIsTyping(true);
|
|
207
|
+
setQuery(e.target.value);
|
|
208
|
+
}}
|
|
209
|
+
autoFocus={autoFocus}
|
|
210
|
+
placeholder={placeholder}
|
|
211
|
+
onKeyDown={(e) => {
|
|
212
|
+
if (e.key === "Escape") {
|
|
213
|
+
e.stopPropagation();
|
|
214
|
+
close();
|
|
215
|
+
} else {
|
|
216
|
+
onKeyDown(e);
|
|
217
|
+
}
|
|
218
|
+
}}
|
|
219
|
+
id={id}
|
|
220
|
+
onClick={() => setIsTyping(true)}
|
|
221
|
+
onBlur={(e) => {
|
|
222
|
+
if (
|
|
223
|
+
e.relatedTarget === null ||
|
|
224
|
+
!(e.relatedTarget as HTMLElement).closest?.(
|
|
225
|
+
".roamjs-autocomplete-input"
|
|
226
|
+
)
|
|
227
|
+
) {
|
|
228
|
+
setIsTyping(false);
|
|
229
|
+
}
|
|
230
|
+
if (onBlur) {
|
|
231
|
+
onBlur(e.target.value);
|
|
232
|
+
}
|
|
233
|
+
}}
|
|
234
|
+
inputRef={inputRef}
|
|
235
|
+
{...(showButton
|
|
236
|
+
? {
|
|
237
|
+
rightElement: (
|
|
238
|
+
<Button icon={"add"} minimal onClick={() => onEnter()} />
|
|
239
|
+
),
|
|
240
|
+
}
|
|
241
|
+
: {})}
|
|
242
|
+
/>
|
|
243
|
+
}
|
|
244
|
+
/>
|
|
245
|
+
);
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
export default AutocompleteInput;
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import createBlock from "../writes/createBlock";
|
|
3
3
|
|
|
4
|
-
type BlockErrorBoundaryProps = {
|
|
4
|
+
type BlockErrorBoundaryProps = {
|
|
5
|
+
blockUid: string;
|
|
6
|
+
message: string;
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
};
|
|
5
9
|
type BlockErrorBoundaryState = { hasError: boolean };
|
|
6
10
|
|
|
7
11
|
class BlockErrorBoundary extends React.Component<
|