@parallelworks/ui 0.77.0 → 0.79.0
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/README.md +42 -6
- package/dist/access/AccessDrawer.d.ts +49 -7
- package/dist/access/AccessDrawer.js +243 -38
- package/dist/access/AccessList.d.ts +19 -1
- package/dist/access/AccessList.js +220 -9
- package/dist/access/GrantView.d.ts +3 -1
- package/dist/access/GrantView.js +17 -4
- package/dist/access/PrincipalPicker.js +5 -5
- package/dist/access/focus.js +1 -1
- package/dist/access/index.d.ts +2 -2
- package/dist/access/index.js +2 -2
- package/dist/access/strings.d.ts +25 -1
- package/dist/access/strings.js +27 -3
- package/dist/access/types.d.ts +26 -0
- package/dist/ai/components/AllocationSelector.js +1 -1
- package/dist/ai/components/AttachmentManager.js +5 -5
- package/dist/ai/components/AttachmentUpload.js +2 -2
- package/dist/ai/components/BranchNavigator.js +1 -1
- package/dist/ai/components/ChatActivity.js +1 -1
- package/dist/ai/components/ChatEmptyState.js +3 -3
- package/dist/ai/components/ChatInput.js +3 -5
- package/dist/ai/components/ChatLayout.js +1 -1
- package/dist/ai/components/ChatMessage.js +1 -1
- package/dist/ai/components/ChatMessageList.js +23 -8
- package/dist/ai/components/ChatSidebar.js +3 -3
- package/dist/ai/components/ChatThread.js +4 -4
- package/dist/ai/components/ComposerAddMenu.js +9 -20
- package/dist/ai/components/ModelSelector.js +3 -3
- package/dist/ai/components/PastedText.js +5 -5
- package/dist/ai/components/SessionView.js +1 -1
- package/dist/ai/components/ShareDialog.js +3 -3
- package/dist/ai/components/SlashMenu.d.ts +31 -4
- package/dist/ai/components/SlashMenu.js +36 -63
- package/dist/ai/components/agent/ActivityLine.js +1 -1
- package/dist/ai/components/agent/AgentMessageParts.js +13 -13
- package/dist/ai/components/conversationActions.js +3 -3
- package/dist/ai/core/ChatProvider.js +1 -1
- package/dist/ai/core/config.js +1 -1
- package/dist/ai/ui/ConversationSidebar.js +3 -3
- package/dist/ai/ui/sidebar.js +3 -3
- package/dist/assets/yaml.worker-DOBV2lND.js +884 -0
- package/dist/components/ActionMenu.d.ts +38 -9
- package/dist/components/ActionMenu.js +48 -22
- package/dist/components/Avatar.d.ts +66 -6
- package/dist/components/Avatar.js +130 -18
- package/dist/components/Breadcrumbs.d.ts +4 -0
- package/dist/components/Breadcrumbs.js +26 -10
- package/dist/components/Button.d.ts +3 -2
- package/dist/components/Button.js +11 -1
- package/dist/components/Callout.d.ts +3 -2
- package/dist/components/Callout.js +3 -2
- package/dist/components/Card.d.ts +33 -9
- package/dist/components/Card.js +24 -8
- package/dist/components/Checkbox.d.ts +3 -1
- package/dist/components/Checkbox.js +4 -2
- package/dist/components/ColorInput.js +1 -1
- package/dist/components/Combobox.d.ts +23 -0
- package/dist/components/Combobox.js +252 -86
- package/dist/components/ConfirmModal.d.ts +28 -2
- package/dist/components/ConfirmModal.js +100 -15
- package/dist/components/ContextMenu.d.ts +29 -2
- package/dist/components/ContextMenu.js +60 -8
- package/dist/components/CopyButton.d.ts +21 -8
- package/dist/components/CopyButton.js +29 -9
- package/dist/components/CreateModal.d.ts +20 -4
- package/dist/components/CreateModal.js +28 -17
- package/dist/components/DatePicker.d.ts +31 -1
- package/dist/components/DatePicker.js +178 -47
- package/dist/components/DescriptionList.d.ts +13 -3
- package/dist/components/DescriptionList.js +43 -11
- package/dist/components/Disclosure.d.ts +10 -2
- package/dist/components/Disclosure.js +18 -7
- package/dist/components/Drawer.js +5 -5
- package/dist/components/EmptyState.d.ts +12 -4
- package/dist/components/EmptyState.js +18 -10
- package/dist/components/ErrorPage.d.ts +40 -0
- package/dist/components/ErrorPage.js +101 -0
- package/dist/components/ExternalLink.d.ts +4 -1
- package/dist/components/ExternalLink.js +4 -5
- package/dist/components/Field.d.ts +21 -6
- package/dist/components/Field.js +32 -16
- package/dist/components/FileButton.d.ts +19 -0
- package/dist/components/FileButton.js +32 -0
- package/dist/components/HoverCard.d.ts +26 -6
- package/dist/components/HoverCard.js +11 -5
- package/dist/components/IconButton.js +4 -2
- package/dist/components/InfoTip.js +3 -3
- package/dist/components/Input.d.ts +33 -3
- package/dist/components/Input.js +149 -14
- package/dist/components/Kbd.d.ts +40 -0
- package/dist/components/Kbd.js +168 -0
- package/dist/components/Listbox.d.ts +103 -0
- package/dist/components/Listbox.js +190 -0
- package/dist/components/Loader.d.ts +8 -2
- package/dist/components/Loader.js +16 -1
- package/dist/components/Markdown.js +1 -1
- package/dist/components/MarkdownImpl.js +1 -1
- package/dist/components/Menu.d.ts +20 -2
- package/dist/components/Menu.js +48 -19
- package/dist/components/MenuEntries.d.ts +41 -4
- package/dist/components/MenuEntries.js +266 -39
- package/dist/components/Meter.d.ts +50 -11
- package/dist/components/Meter.js +71 -21
- package/dist/components/Modal.d.ts +9 -3
- package/dist/components/Modal.js +68 -29
- package/dist/components/OneTimeCodeInput.js +1 -1
- package/dist/components/PageHeader.js +111 -7
- package/dist/components/PageHeaderAction.d.ts +8 -3
- package/dist/components/PageHeaderAction.js +122 -34
- package/dist/components/Pagination.d.ts +13 -1
- package/dist/components/Pagination.js +90 -19
- package/dist/components/Popover.d.ts +16 -2
- package/dist/components/Popover.js +20 -21
- package/dist/components/Provider.d.ts +172 -5
- package/dist/components/Provider.js +157 -14
- package/dist/components/RefreshButton.d.ts +13 -6
- package/dist/components/RefreshButton.js +17 -9
- package/dist/components/RunStatusIcon.d.ts +16 -7
- package/dist/components/RunStatusIcon.js +21 -8
- package/dist/components/Section.d.ts +11 -4
- package/dist/components/Section.js +33 -22
- package/dist/components/SegmentedControl.d.ts +16 -3
- package/dist/components/SegmentedControl.js +7 -3
- package/dist/components/Select.d.ts +19 -3
- package/dist/components/Select.js +114 -68
- package/dist/components/SelectionCopy.d.ts +10 -0
- package/dist/components/SelectionCopy.js +105 -0
- package/dist/components/SettingModeOption.js +1 -1
- package/dist/components/SettingsCard.js +1 -1
- package/dist/components/Skeleton.js +1 -1
- package/dist/components/StatusBadge.d.ts +55 -10
- package/dist/components/StatusBadge.js +91 -15
- package/dist/components/StickyBar.js +1 -1
- package/dist/components/Table.d.ts +22 -4
- package/dist/components/Table.js +46 -24
- package/dist/components/Tabs.d.ts +9 -4
- package/dist/components/Tabs.js +9 -4
- package/dist/components/Tag.d.ts +49 -0
- package/dist/components/Tag.js +199 -0
- package/dist/components/ThemeEditor.js +1 -1
- package/dist/components/Toast.js +1 -1
- package/dist/components/ToggleGroup.d.ts +4 -1
- package/dist/components/ToggleGroup.js +30 -13
- package/dist/components/Tooltip.d.ts +9 -3
- package/dist/components/Tooltip.js +55 -39
- package/dist/components/buttonClasses.d.ts +5 -2
- package/dist/components/buttonClasses.js +11 -10
- package/dist/components/chart.d.ts +69 -0
- package/dist/components/chart.js +145 -0
- package/dist/components/comboboxItems.d.ts +7 -1
- package/dist/components/comboboxItems.js +6 -2
- package/dist/components/dismiss.d.ts +5 -1
- package/dist/components/dismiss.js +6 -2
- package/dist/components/floating.d.ts +4 -4
- package/dist/components/floating.js +5 -5
- package/dist/components/headerActionsFit.d.ts +37 -0
- package/dist/components/headerActionsFit.js +81 -0
- package/dist/components/pageHeaderStore.d.ts +3 -0
- package/dist/components/pageHeaderStore.js +3 -0
- package/dist/components/pathBreaks.js +2 -2
- package/dist/components/popupContainer.d.ts +17 -0
- package/dist/components/popupContainer.js +69 -0
- package/dist/components/relativeTime.d.ts +21 -10
- package/dist/components/relativeTime.js +50 -22
- package/dist/components/segmented.d.ts +18 -4
- package/dist/components/segmented.js +48 -6
- package/dist/components/typedDate.d.ts +5 -0
- package/dist/components/typedDate.js +50 -0
- package/dist/components/useClosingValue.d.ts +4 -0
- package/dist/components/useClosingValue.js +12 -0
- package/dist/components/useCopy.d.ts +6 -0
- package/dist/components/useCopy.js +3 -3
- package/dist/components/useNarrow.d.ts +2 -0
- package/dist/components/useNarrow.js +16 -0
- package/dist/editor/Editor.js +1 -1
- package/dist/editor/FormCodePanel.js +1 -1
- package/dist/editor/Monaco.js +1 -1
- package/dist/editor/workers.js +1 -1
- package/dist/file-explorer/FileExplorer.js +16 -15
- package/dist/file-explorer/FileExplorerContext.js +2 -2
- package/dist/file-explorer/FilePreview/Viewer.js +3 -3
- package/dist/file-explorer/FilePreview/index.js +2 -2
- package/dist/file-explorer/FileRowsWindow.js +3 -3
- package/dist/file-explorer/FileTable.js +1 -1
- package/dist/file-explorer/LoadMoreRow.js +1 -1
- package/dist/file-explorer/TreeView.js +5 -5
- package/dist/file-explorer/helpers.js +2 -2
- package/dist/file-explorer/toasts.js +2 -2
- package/dist/form/Fields/CheckboxGroupField.js +1 -1
- package/dist/form/Fields/DropdownField.js +1 -1
- package/dist/form/Fields/DurationField.js +2 -2
- package/dist/form/Fields/MultiDropdownField.js +1 -1
- package/dist/form/Fields/NumberField.js +1 -1
- package/dist/form/Fields/ObjectField.js +1 -1
- package/dist/form/Fields/RangeField.js +1 -1
- package/dist/form/Fields/StringField.js +1 -1
- package/dist/form/Form.js +3 -3
- package/dist/form/FormLayout.js +1 -1
- package/dist/form/Wizard/WizardContainer.js +1 -1
- package/dist/form/Wizard/WizardNavigation.js +1 -1
- package/dist/form/Wizard/WizardStepContent.js +1 -1
- package/dist/form/fieldRegistry.js +1 -1
- package/dist/form/formEditing.js +1 -1
- package/dist/format.d.ts +17 -7
- package/dist/format.js +39 -16
- package/dist/graph/AnnotationBanner.js +2 -2
- package/dist/graph/Connectors.js +5 -5
- package/dist/graph/DependencyGraph.js +10 -9
- package/dist/graph/GraphEditor.js +4 -4
- package/dist/graph/GraphEditorDialogs.js +13 -12
- package/dist/graph/GraphNode.js +1 -1
- package/dist/graph/InputDialog.js +5 -5
- package/dist/graph/InputsEditor.js +10 -9
- package/dist/graph/MatrixGroup.js +2 -2
- package/dist/graph/ProblemPageLink.d.ts +9 -0
- package/dist/graph/ProblemPageLink.js +21 -0
- package/dist/graph/ProblemsButton.js +26 -16
- package/dist/graph/Reveal.js +1 -1
- package/dist/graph/RunWarningsList.js +21 -12
- package/dist/graph/ShortcutsButton.js +25 -8
- package/dist/graph/StepSummaries.js +1 -1
- package/dist/graph/TreeView.js +2 -2
- package/dist/graph/WorkflowSettingsDialog.js +3 -3
- package/dist/graph/editorApi.d.ts +10 -0
- package/dist/graph/editorFields.js +3 -3
- package/dist/graph/editorPrimitives.js +1 -1
- package/dist/graph/editorRows.js +8 -6
- package/dist/graph/editorStrings.d.ts +1 -0
- package/dist/graph/editorStrings.js +1 -0
- package/dist/graph/fieldProblems.js +2 -2
- package/dist/graph/inputRefs.js +7 -6
- package/dist/graph/runWarnings.d.ts +12 -1
- package/dist/graph/stepWith.js +2 -2
- package/dist/index.d.ts +30 -21
- package/dist/index.js +20 -12
- package/dist/list/ActionToolbar.d.ts +4 -0
- package/dist/list/ActionToolbar.js +35 -12
- package/dist/list/ListFilterBar.d.ts +42 -0
- package/dist/list/ListFilterBar.js +789 -0
- package/dist/list/ListPage.d.ts +42 -4
- package/dist/list/ListPage.js +56 -22
- package/dist/list/ListTable.d.ts +27 -3
- package/dist/list/ListTable.js +48 -23
- package/dist/list/ListToolbar.d.ts +31 -0
- package/dist/list/ListToolbar.js +43 -0
- package/dist/list/ListViewControls.d.ts +8 -4
- package/dist/list/ListViewControls.js +22 -14
- package/dist/list/UserHoverCard.d.ts +8 -2
- package/dist/list/UserHoverCard.js +26 -13
- package/dist/list/filterModel.d.ts +122 -0
- package/dist/list/filterModel.js +308 -0
- package/dist/list/index.d.ts +6 -2
- package/dist/list/index.js +6 -2
- package/dist/list/listSearch.d.ts +1 -1
- package/dist/list/listSearch.js +8 -4
- package/dist/list/listView.d.ts +24 -2
- package/dist/list/listView.js +47 -45
- package/dist/list/rowMenu.d.ts +10 -5
- package/dist/list/rowMenu.js +17 -7
- package/dist/list/useListFilters.d.ts +68 -0
- package/dist/list/useListFilters.js +252 -0
- package/dist/logviewer/LogViewer.js +4 -4
- package/dist/styles/theme.css +94 -0
- package/dist/styles.css +1 -1
- package/package.json +11 -11
- package/dist/assets/yaml.worker-k9AdOqzf.js +0 -1441
- package/dist/components/fullscreen.d.ts +0 -4
- package/dist/components/fullscreen.js +0 -18
package/dist/components/Input.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { cx } from "../cx.js";
|
|
2
2
|
import { fieldBox } from "./fieldBox.js";
|
|
3
3
|
import { Field, RevealToggle } from "./Field.js";
|
|
4
|
+
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
4
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
-
import { useState } from "react";
|
|
6
6
|
import { Input } from "@base-ui/react/input";
|
|
7
|
+
import { flushSync } from "react-dom";
|
|
7
8
|
//#region src/components/Input.tsx
|
|
8
9
|
/** The Field around a control when it has a label; the control alone when it doesn't. */
|
|
9
10
|
function framed({ label, hint, help, actions, description, error, size }, { id, required, disabled }, control) {
|
|
@@ -61,9 +62,48 @@ function Input$1({ label, hint, help, description, error, className, mono, size,
|
|
|
61
62
|
}) : input;
|
|
62
63
|
});
|
|
63
64
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
var boxEdges = {
|
|
66
|
+
sm: "0.5rem + 2px",
|
|
67
|
+
md: "0.75rem + 2px",
|
|
68
|
+
lg: "1rem + 2px"
|
|
69
|
+
};
|
|
70
|
+
var sizesToContent = () => typeof CSS !== "undefined" && CSS.supports?.("field-sizing", "content") === true;
|
|
71
|
+
/** Sets a textarea's height to its text's while the browser can't, from its value. */
|
|
72
|
+
function useAutosize(enabled, value) {
|
|
73
|
+
const element = useRef(null);
|
|
74
|
+
const [native] = useState(sizesToContent);
|
|
75
|
+
const resize = useCallback(() => {
|
|
76
|
+
const el = element.current;
|
|
77
|
+
if (!el || native) return;
|
|
78
|
+
el.style.height = "auto";
|
|
79
|
+
el.style.height = `${el.scrollHeight + el.offsetHeight - el.clientHeight}px`;
|
|
80
|
+
}, [native]);
|
|
81
|
+
useLayoutEffect(() => {
|
|
82
|
+
if (enabled) resize();
|
|
83
|
+
}, [
|
|
84
|
+
enabled,
|
|
85
|
+
resize,
|
|
86
|
+
value
|
|
87
|
+
]);
|
|
88
|
+
return {
|
|
89
|
+
element,
|
|
90
|
+
resize: enabled && !native ? resize : void 0
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/** A multi-line text field that grows by hand, or with its text with `autosize`. Placed in a
|
|
94
|
+
* Field, it takes the Field's label, description and invalid state. */
|
|
95
|
+
function Textarea({ label, hint, help, description, error, className, mono, size = "md", ref, autosize, bare = false, secret = false, style, ...props }) {
|
|
96
|
+
const [revealed, setRevealed] = useState(false);
|
|
97
|
+
const autosizing = Boolean(autosize);
|
|
98
|
+
const maxRows = typeof autosize === "object" ? autosize.maxRows : void 0;
|
|
99
|
+
const { element, resize } = useAutosize(autosizing, props.value);
|
|
100
|
+
const edges = bare ? "0px" : boxEdges[size];
|
|
101
|
+
const setRef = useCallback((el) => {
|
|
102
|
+
element.current = el;
|
|
103
|
+
if (typeof ref === "function") ref(el);
|
|
104
|
+
else if (ref) ref.current = el;
|
|
105
|
+
}, [ref, element]);
|
|
106
|
+
const masked = secret && !revealed;
|
|
67
107
|
return framed({
|
|
68
108
|
label,
|
|
69
109
|
hint,
|
|
@@ -71,16 +111,111 @@ function Textarea({ label, hint, help, description, error, className, mono, size
|
|
|
71
111
|
description,
|
|
72
112
|
error,
|
|
73
113
|
size
|
|
74
|
-
}, props, (control) =>
|
|
114
|
+
}, props, (control) => {
|
|
115
|
+
const textarea = /* @__PURE__ */ jsx(Input, {
|
|
116
|
+
...secret && {
|
|
117
|
+
spellCheck: false,
|
|
118
|
+
autoComplete: "off",
|
|
119
|
+
autoCorrect: "off",
|
|
120
|
+
autoCapitalize: "off",
|
|
121
|
+
"data-1p-ignore": true
|
|
122
|
+
},
|
|
123
|
+
...props,
|
|
124
|
+
...joined(control, props),
|
|
125
|
+
onInput: (event) => {
|
|
126
|
+
resize?.();
|
|
127
|
+
props.onInput?.(event);
|
|
128
|
+
},
|
|
129
|
+
ref: setRef,
|
|
130
|
+
render: /* @__PURE__ */ jsx("textarea", {}),
|
|
131
|
+
style: {
|
|
132
|
+
...autosizing && {
|
|
133
|
+
minHeight: `calc(${props.rows ?? 2}lh + ${edges})`,
|
|
134
|
+
...maxRows !== void 0 && { maxHeight: `calc(${maxRows}lh + ${edges})` }
|
|
135
|
+
},
|
|
136
|
+
...style
|
|
137
|
+
},
|
|
138
|
+
className: cx(bare ? "w-full min-w-0 border-0 bg-transparent p-0 text-(--theme-app) outline-none placeholder:text-(--theme-placeholder) disabled:cursor-not-allowed disabled:text-(--theme-placeholder)" : fieldBox({
|
|
139
|
+
size,
|
|
140
|
+
multiline: true
|
|
141
|
+
}), bare && {
|
|
142
|
+
sm: "text-xs",
|
|
143
|
+
md: "text-sm",
|
|
144
|
+
lg: "text-base"
|
|
145
|
+
}[size], autosizing ? "resize-none [field-sizing:content]" : cx(size === "sm" ? "min-h-16" : "min-h-24", !bare && "resize-y"), bare && !autosizing && "resize-none", (mono || secret) && "font-mono", secret && "pr-9", masked && "[-webkit-text-security:disc]", className)
|
|
146
|
+
});
|
|
147
|
+
return secret ? /* @__PURE__ */ jsxs("div", {
|
|
148
|
+
className: "relative w-full",
|
|
149
|
+
children: [textarea, /* @__PURE__ */ jsx(RevealToggle, {
|
|
150
|
+
revealed,
|
|
151
|
+
onRevealedChange: setRevealed,
|
|
152
|
+
disabled: props.disabled,
|
|
153
|
+
align: "top",
|
|
154
|
+
size
|
|
155
|
+
})]
|
|
156
|
+
}) : textarea;
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
/** A title edited where it reads: one line that wraps and grows, with no box until the
|
|
160
|
+
* pointer or focus is on it. Enter saves, Escape puts the saved title back, and leaving the
|
|
161
|
+
* field saves. */
|
|
162
|
+
function TitleInput({ value, onCommit, autoSelect = false, size = "xl", readOnly, className, onBlur, onFocus, onKeyDown, ref, ...props }) {
|
|
163
|
+
const [draft, setDraft] = useState(value);
|
|
164
|
+
const editing = useRef(false);
|
|
165
|
+
const element = useRef(null);
|
|
166
|
+
const [shown, setShown] = useState(value);
|
|
167
|
+
if (value !== shown) {
|
|
168
|
+
setShown(value);
|
|
169
|
+
if (!editing.current) setDraft(value);
|
|
170
|
+
}
|
|
171
|
+
const { resize } = useAutosize(true, draft);
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
if (autoSelect && element.current) {
|
|
174
|
+
element.current.focus();
|
|
175
|
+
element.current.select();
|
|
176
|
+
}
|
|
177
|
+
}, []);
|
|
178
|
+
const setRef = useCallback((el) => {
|
|
179
|
+
element.current = el;
|
|
180
|
+
if (typeof ref === "function") ref(el);
|
|
181
|
+
else if (ref) ref.current = el;
|
|
182
|
+
}, [ref]);
|
|
183
|
+
return /* @__PURE__ */ jsx("textarea", {
|
|
75
184
|
...props,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
185
|
+
ref: setRef,
|
|
186
|
+
rows: 1,
|
|
187
|
+
value: draft,
|
|
188
|
+
readOnly,
|
|
189
|
+
spellCheck: props.spellCheck ?? true,
|
|
190
|
+
onChange: (event) => {
|
|
191
|
+
setDraft(event.target.value.replace(/\s*\n+\s*/g, " "));
|
|
192
|
+
resize?.();
|
|
193
|
+
},
|
|
194
|
+
onFocus: (event) => {
|
|
195
|
+
editing.current = true;
|
|
196
|
+
onFocus?.(event);
|
|
197
|
+
},
|
|
198
|
+
onBlur: (event) => {
|
|
199
|
+
editing.current = false;
|
|
200
|
+
const title = draft.trim();
|
|
201
|
+
if (title && title !== value) onCommit(title);
|
|
202
|
+
else setDraft(value);
|
|
203
|
+
onBlur?.(event);
|
|
204
|
+
},
|
|
205
|
+
onKeyDown: (event) => {
|
|
206
|
+
onKeyDown?.(event);
|
|
207
|
+
if (event.defaultPrevented || event.nativeEvent.isComposing) return;
|
|
208
|
+
if (event.key === "Enter") {
|
|
209
|
+
event.preventDefault();
|
|
210
|
+
event.currentTarget.blur();
|
|
211
|
+
} else if (event.key === "Escape") {
|
|
212
|
+
event.preventDefault();
|
|
213
|
+
flushSync(() => setDraft(value));
|
|
214
|
+
event.currentTarget.blur();
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
className: cx("-mx-1.5 block w-[calc(100%+0.75rem)] min-w-0 resize-none overflow-hidden rounded-md border-0 bg-transparent px-1.5 py-0.5 font-semibold tracking-tight text-(--theme-app) outline-none transition-colors [field-sizing:content] placeholder:text-(--theme-placeholder)", size === "xl" ? "text-2xl leading-tight" : "text-lg leading-snug", !readOnly && "hover:bg-(--theme-hover) focus-visible:bg-transparent focus-visible:ring-1 focus-visible:ring-(--theme-focus)", className)
|
|
218
|
+
});
|
|
84
219
|
}
|
|
85
220
|
//#endregion
|
|
86
|
-
export { Input$1 as Input, Textarea, framed };
|
|
221
|
+
export { Input$1 as Input, Textarea, TitleInput, framed };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type UIStrings } from './Provider.js';
|
|
2
|
+
type KeyNames = UIStrings['keys'];
|
|
3
|
+
/** Whether the keyboard is an Apple one, whose Command key does what Control does elsewhere. */
|
|
4
|
+
export declare function isApplePlatform(): boolean;
|
|
5
|
+
/** A shortcut's platform: `apple` shows ⌘ ⌥ ⇧ ⌃ and takes Command for `Mod`; `other` names
|
|
6
|
+
* the keys and takes Control. */
|
|
7
|
+
export type KeyPlatform = 'apple' | 'other';
|
|
8
|
+
/** The keys of a shortcut as its keycaps read: `Mod+K` is ⌘ K on an Apple keyboard and Ctrl K
|
|
9
|
+
* elsewhere. A string is a combination joined by `+`; an array lists its keys as given, such
|
|
10
|
+
* as `['[', ']']` or `['1–9']`. Pass `names` from the strings, so key names are localized. */
|
|
11
|
+
export declare function formatShortcut(keys: string | readonly string[], names?: KeyNames, platform?: KeyPlatform): string[];
|
|
12
|
+
/** A shortcut as one line of text, for a title or a sentence: ⌘K, or Ctrl+K. */
|
|
13
|
+
export declare function shortcutText(keys: string | readonly string[], names?: KeyNames, platform?: KeyPlatform): string;
|
|
14
|
+
/** The `aria-keyshortcuts` value for a combination: `Mod+K` is `Meta+K` on an Apple keyboard
|
|
15
|
+
* and `Control+K` elsewhere. Set it on the control the shortcut presses. */
|
|
16
|
+
export declare function ariaKeyShortcuts(keys: string, platform?: KeyPlatform): string;
|
|
17
|
+
/** `formatShortcut` and `shortcutText` in the host's key names. */
|
|
18
|
+
export declare function useShortcutFormat(platform?: KeyPlatform): {
|
|
19
|
+
format: (keys: string | readonly string[]) => string[];
|
|
20
|
+
text: (keys: string | readonly string[]) => string;
|
|
21
|
+
};
|
|
22
|
+
export type KbdVariant = 'keycap' | 'plain';
|
|
23
|
+
export type KbdSize = 'xs' | 'sm';
|
|
24
|
+
export interface KbdProps {
|
|
25
|
+
/** A combination joined by `+`, such as `Mod+K` (Command on an Apple keyboard, Control
|
|
26
|
+
* elsewhere) or `Shift+Enter`; or keys side by side, as given: `['↑', '↓']`, `['1–9']`. */
|
|
27
|
+
keys: string | readonly string[];
|
|
28
|
+
/** `keycap`, the default, draws each key; `plain` is muted text, for a menu row. */
|
|
29
|
+
variant?: KbdVariant | undefined;
|
|
30
|
+
/** `xs` (16px, 10px text) for a hint inside a row or a field; `sm` (18px, 11px) otherwise. */
|
|
31
|
+
size?: KbdSize | undefined;
|
|
32
|
+
/** Which keyboard to show the keys for; the reader's own by default. */
|
|
33
|
+
platform?: KeyPlatform | undefined;
|
|
34
|
+
className?: string | undefined;
|
|
35
|
+
}
|
|
36
|
+
/** Keys to press, as keycaps: a muted fill, a hairline border and monospace text. Off an
|
|
37
|
+
* Apple keyboard the keys of a combination are joined with a plus, as the platform writes
|
|
38
|
+
* them. Renders `<kbd>`, with a `<kbd>` for each key of a combination. */
|
|
39
|
+
export declare function Kbd({ keys, variant, size, platform, className }: KbdProps): import("react").JSX.Element;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { cx } from "../cx.js";
|
|
2
|
+
import { useStrings } from "./Provider.js";
|
|
3
|
+
import { Fragment } from "react";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
//#region src/components/Kbd.tsx
|
|
6
|
+
/** Whether the keyboard is an Apple one, whose Command key does what Control does elsewhere. */
|
|
7
|
+
function isApplePlatform() {
|
|
8
|
+
if (typeof navigator === "undefined") return false;
|
|
9
|
+
const data = navigator.userAgentData;
|
|
10
|
+
return /Mac|iPhone|iPad|iPod/i.test(data?.platform || navigator.userAgent);
|
|
11
|
+
}
|
|
12
|
+
var currentPlatform = () => isApplePlatform() ? "apple" : "other";
|
|
13
|
+
var DEFAULT_NAMES = {
|
|
14
|
+
ctrl: "Ctrl",
|
|
15
|
+
alt: "Alt",
|
|
16
|
+
shift: "Shift",
|
|
17
|
+
meta: "Meta",
|
|
18
|
+
enter: "Enter",
|
|
19
|
+
escape: "Esc",
|
|
20
|
+
space: "Space",
|
|
21
|
+
tab: "Tab",
|
|
22
|
+
backspace: "Backspace",
|
|
23
|
+
delete: "Delete"
|
|
24
|
+
};
|
|
25
|
+
var ALIASES = {
|
|
26
|
+
mod: "mod",
|
|
27
|
+
cmd: "meta",
|
|
28
|
+
command: "meta",
|
|
29
|
+
meta: "meta",
|
|
30
|
+
ctrl: "ctrl",
|
|
31
|
+
control: "ctrl",
|
|
32
|
+
alt: "alt",
|
|
33
|
+
option: "alt",
|
|
34
|
+
opt: "alt",
|
|
35
|
+
shift: "shift",
|
|
36
|
+
enter: "enter",
|
|
37
|
+
return: "enter",
|
|
38
|
+
esc: "escape",
|
|
39
|
+
escape: "escape",
|
|
40
|
+
space: "space",
|
|
41
|
+
tab: "tab",
|
|
42
|
+
backspace: "backspace",
|
|
43
|
+
delete: "delete",
|
|
44
|
+
del: "delete",
|
|
45
|
+
up: "up",
|
|
46
|
+
arrowup: "up",
|
|
47
|
+
down: "down",
|
|
48
|
+
arrowdown: "down",
|
|
49
|
+
left: "left",
|
|
50
|
+
arrowleft: "left",
|
|
51
|
+
right: "right",
|
|
52
|
+
arrowright: "right"
|
|
53
|
+
};
|
|
54
|
+
var ARROWS = {
|
|
55
|
+
up: "↑",
|
|
56
|
+
down: "↓",
|
|
57
|
+
left: "←",
|
|
58
|
+
right: "→"
|
|
59
|
+
};
|
|
60
|
+
var APPLE = {
|
|
61
|
+
meta: "⌘",
|
|
62
|
+
alt: "⌥",
|
|
63
|
+
shift: "⇧",
|
|
64
|
+
ctrl: "⌃",
|
|
65
|
+
enter: "↵"
|
|
66
|
+
};
|
|
67
|
+
var ARIA = {
|
|
68
|
+
meta: "Meta",
|
|
69
|
+
ctrl: "Control",
|
|
70
|
+
alt: "Alt",
|
|
71
|
+
shift: "Shift",
|
|
72
|
+
enter: "Enter",
|
|
73
|
+
escape: "Escape",
|
|
74
|
+
space: "Space",
|
|
75
|
+
tab: "Tab",
|
|
76
|
+
backspace: "Backspace",
|
|
77
|
+
delete: "Delete",
|
|
78
|
+
up: "ArrowUp",
|
|
79
|
+
down: "ArrowDown",
|
|
80
|
+
left: "ArrowLeft",
|
|
81
|
+
right: "ArrowRight"
|
|
82
|
+
};
|
|
83
|
+
/** Splits a combination such as `Mod+Shift+K` into its keys; `Mod++` ends with the plus key. */
|
|
84
|
+
function splitCombo(keys) {
|
|
85
|
+
const trimmed = keys.trim();
|
|
86
|
+
if (trimmed === "+") return ["+"];
|
|
87
|
+
const plusKey = trimmed.endsWith("++");
|
|
88
|
+
const parts = (plusKey ? trimmed.slice(0, -2) : trimmed).split("+").map((part) => part.trim()).filter(Boolean);
|
|
89
|
+
return plusKey ? [...parts, "+"] : parts;
|
|
90
|
+
}
|
|
91
|
+
function canonical(key, platform) {
|
|
92
|
+
const name = ALIASES[key.toLowerCase()];
|
|
93
|
+
if (name === "mod") return platform === "apple" ? "meta" : "ctrl";
|
|
94
|
+
return name;
|
|
95
|
+
}
|
|
96
|
+
function label(key, names, platform) {
|
|
97
|
+
const name = canonical(key, platform);
|
|
98
|
+
if (!name) return key.length === 1 ? key.toUpperCase() : key;
|
|
99
|
+
if (ARROWS[name]) return ARROWS[name];
|
|
100
|
+
if (platform === "apple" && APPLE[name]) return APPLE[name];
|
|
101
|
+
return names[name] ?? key;
|
|
102
|
+
}
|
|
103
|
+
/** The keys of a shortcut as its keycaps read: `Mod+K` is ⌘ K on an Apple keyboard and Ctrl K
|
|
104
|
+
* elsewhere. A string is a combination joined by `+`; an array lists its keys as given, such
|
|
105
|
+
* as `['[', ']']` or `['1–9']`. Pass `names` from the strings, so key names are localized. */
|
|
106
|
+
function formatShortcut(keys, names = DEFAULT_NAMES, platform = currentPlatform()) {
|
|
107
|
+
return (typeof keys === "string" ? splitCombo(keys) : keys).map((key) => label(key, names, platform));
|
|
108
|
+
}
|
|
109
|
+
/** A shortcut as one line of text, for a title or a sentence: ⌘K, or Ctrl+K. */
|
|
110
|
+
function shortcutText(keys, names = DEFAULT_NAMES, platform = currentPlatform()) {
|
|
111
|
+
const parts = formatShortcut(keys, names, platform);
|
|
112
|
+
if (typeof keys !== "string") return parts.join(" ");
|
|
113
|
+
return parts.join(platform === "apple" ? "" : "+");
|
|
114
|
+
}
|
|
115
|
+
/** The `aria-keyshortcuts` value for a combination: `Mod+K` is `Meta+K` on an Apple keyboard
|
|
116
|
+
* and `Control+K` elsewhere. Set it on the control the shortcut presses. */
|
|
117
|
+
function ariaKeyShortcuts(keys, platform = currentPlatform()) {
|
|
118
|
+
return splitCombo(keys).map((key) => {
|
|
119
|
+
const name = canonical(key, platform);
|
|
120
|
+
if (name) return ARIA[name] ?? key;
|
|
121
|
+
return key.length === 1 ? key.toUpperCase() : key;
|
|
122
|
+
}).join("+");
|
|
123
|
+
}
|
|
124
|
+
/** `formatShortcut` and `shortcutText` in the host's key names. */
|
|
125
|
+
function useShortcutFormat(platform) {
|
|
126
|
+
const { keys: names } = useStrings();
|
|
127
|
+
const on = platform ?? currentPlatform();
|
|
128
|
+
return {
|
|
129
|
+
format: (keys) => formatShortcut(keys, names, on),
|
|
130
|
+
text: (keys) => shortcutText(keys, names, on)
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
var keycap = {
|
|
134
|
+
xs: "min-h-4 min-w-4 px-[3px] py-px text-[10px]",
|
|
135
|
+
sm: "min-h-[18px] min-w-[18px] px-1 py-px text-[11px]"
|
|
136
|
+
};
|
|
137
|
+
/** Keys to press, as keycaps: a muted fill, a hairline border and monospace text. Off an
|
|
138
|
+
* Apple keyboard the keys of a combination are joined with a plus, as the platform writes
|
|
139
|
+
* them. Renders `<kbd>`, with a `<kbd>` for each key of a combination. */
|
|
140
|
+
function Kbd({ keys, variant = "keycap", size = "sm", platform, className }) {
|
|
141
|
+
const { keys: names } = useStrings();
|
|
142
|
+
const on = platform ?? currentPlatform();
|
|
143
|
+
const parts = formatShortcut(keys, names, on);
|
|
144
|
+
const chord = typeof keys === "string";
|
|
145
|
+
if (variant === "plain") return /* @__PURE__ */ jsx("kbd", {
|
|
146
|
+
className: cx("font-sans text-(--theme-muted-text-color) tabular-nums tracking-wide", size === "xs" ? "text-[10px]" : "text-[11px]", className),
|
|
147
|
+
children: chord ? shortcutText(keys, names, on) : parts.join(" ")
|
|
148
|
+
});
|
|
149
|
+
const cap = cx("inline-flex items-center justify-center rounded border border-(--theme-border) bg-(--theme-muted-panel-bg) font-mono leading-none font-normal text-(--theme-app) not-italic", keycap[size]);
|
|
150
|
+
if (parts.length === 1) return /* @__PURE__ */ jsx("kbd", {
|
|
151
|
+
className: cx(cap, className),
|
|
152
|
+
children: parts[0]
|
|
153
|
+
});
|
|
154
|
+
const plus = chord && on !== "apple";
|
|
155
|
+
return /* @__PURE__ */ jsx("kbd", {
|
|
156
|
+
className: cx("inline-flex flex-wrap items-center gap-0.5 align-middle", className),
|
|
157
|
+
children: parts.map((part, i) => /* @__PURE__ */ jsxs(Fragment, { children: [plus && i > 0 && /* @__PURE__ */ jsx("span", {
|
|
158
|
+
"aria-hidden": "true",
|
|
159
|
+
className: "text-[10px] text-(--theme-muted-text-color)",
|
|
160
|
+
children: "+"
|
|
161
|
+
}), /* @__PURE__ */ jsx("kbd", {
|
|
162
|
+
className: cap,
|
|
163
|
+
children: part
|
|
164
|
+
})] }, i))
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
//#endregion
|
|
168
|
+
export { Kbd, ariaKeyShortcuts, formatShortcut, isApplePlatform, shortcutText, useShortcutFormat };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { type KeyboardEvent as ReactKeyboardEvent, type ReactNode } from 'react';
|
|
2
|
+
import { type FloatingAnchor } from './floating.js';
|
|
3
|
+
export interface ListboxNavigationOptions<T> {
|
|
4
|
+
items: readonly T[];
|
|
5
|
+
/** Called with the highlighted item on Enter, a `chooseKeys` key or a click. */
|
|
6
|
+
onChoose: (item: T, index: number) => void;
|
|
7
|
+
/** Called on Escape, such as to close the list until the text changes. */
|
|
8
|
+
onDismiss?: (() => void) | undefined;
|
|
9
|
+
/** False while the list isn't shown: the keys are then the host's own. Default true. */
|
|
10
|
+
open?: boolean | undefined;
|
|
11
|
+
/** Past the end comes the start, and before the start the end. Default true. */
|
|
12
|
+
loop?: boolean | undefined;
|
|
13
|
+
/** Keys that choose besides Enter. Default `['Tab']`. */
|
|
14
|
+
chooseKeys?: readonly string[] | undefined;
|
|
15
|
+
/** Home and End move to the first and last items rather than the caret, for a host whose
|
|
16
|
+
* text is a short query rather than prose. Default false. */
|
|
17
|
+
homeEnd?: boolean | undefined;
|
|
18
|
+
/** An item that can't be chosen, which the highlight passes over. */
|
|
19
|
+
isDisabled?: ((item: T) => boolean) | undefined;
|
|
20
|
+
}
|
|
21
|
+
/** Keys are DOM or React keyboard events alike, as an editor plugin and an input pass them. */
|
|
22
|
+
type AnyKeyboardEvent = KeyboardEvent | ReactKeyboardEvent;
|
|
23
|
+
/** The parts a highlighted list needs while focus stays on the box the reader types in, such
|
|
24
|
+
* as a text box, an editor or a search input: the highlight and its keys, and the ids that
|
|
25
|
+
* let assistive technology follow the highlight. Spread `hostProps` on the focused box,
|
|
26
|
+
* `listProps` on the list and `optionProps(i)` on each option, or draw them with
|
|
27
|
+
* ListboxPanel and ListboxOption. */
|
|
28
|
+
export declare function useListboxNavigation<T>({ items, onChoose, onDismiss, open, loop, chooseKeys, homeEnd, isDisabled, }: ListboxNavigationOptions<T>): {
|
|
29
|
+
/** The highlighted item's index, or -1 with nothing to show. */
|
|
30
|
+
active: number;
|
|
31
|
+
setActive: import("react").Dispatch<import("react").SetStateAction<number>>;
|
|
32
|
+
handleKey: (event: AnyKeyboardEvent) => boolean;
|
|
33
|
+
listId: string;
|
|
34
|
+
/** For the box that keeps focus: it points at the list and its highlighted option. */
|
|
35
|
+
hostProps: {
|
|
36
|
+
'aria-autocomplete': 'list';
|
|
37
|
+
'aria-controls': string | undefined;
|
|
38
|
+
'aria-activedescendant': string | undefined;
|
|
39
|
+
};
|
|
40
|
+
listProps: {
|
|
41
|
+
id: string;
|
|
42
|
+
role: 'listbox';
|
|
43
|
+
};
|
|
44
|
+
optionProps: (index: number) => {
|
|
45
|
+
id: string;
|
|
46
|
+
role: 'option';
|
|
47
|
+
tabIndex: number;
|
|
48
|
+
'aria-selected': boolean;
|
|
49
|
+
'aria-disabled': true | undefined;
|
|
50
|
+
'data-highlighted': true | undefined;
|
|
51
|
+
'data-disabled': true | undefined;
|
|
52
|
+
onMouseDown: (event: {
|
|
53
|
+
preventDefault: () => void;
|
|
54
|
+
}) => void;
|
|
55
|
+
onMouseMove: () => void;
|
|
56
|
+
onClick: () => void;
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
export type ListboxNavigation<T> = ReturnType<typeof useListboxNavigation<T>>;
|
|
60
|
+
export interface ListboxPanelProps {
|
|
61
|
+
/** `listProps` from useListboxNavigation. */
|
|
62
|
+
listProps: {
|
|
63
|
+
id: string;
|
|
64
|
+
role: 'listbox';
|
|
65
|
+
};
|
|
66
|
+
/** Names the list for assistive technology. */
|
|
67
|
+
'aria-label': string;
|
|
68
|
+
/** Under the list, such as which keys choose and dismiss. */
|
|
69
|
+
footer?: ReactNode;
|
|
70
|
+
/** In place of the options while there are none. */
|
|
71
|
+
empty?: ReactNode;
|
|
72
|
+
/** Opens at this element or point, such as an editor's caret, over the page and inside a
|
|
73
|
+
* modal dialog alike; without one the panel stands where the host places it. */
|
|
74
|
+
at?: FloatingAnchor | undefined;
|
|
75
|
+
/** With `at`: called when a press outside closes it. */
|
|
76
|
+
onDismiss?: (() => void) | undefined;
|
|
77
|
+
/** On the panel: its width or place. */
|
|
78
|
+
className?: string | undefined;
|
|
79
|
+
children: ReactNode;
|
|
80
|
+
}
|
|
81
|
+
/** The panel of a highlighted list that the reader drives from a box that keeps focus. */
|
|
82
|
+
export declare function ListboxPanel({ listProps, 'aria-label': label, footer, empty, at, onDismiss, className, children, }: ListboxPanelProps): import("react").JSX.Element;
|
|
83
|
+
export interface ListboxOptionProps {
|
|
84
|
+
/** `optionProps(index)` from useListboxNavigation. */
|
|
85
|
+
option: ReturnType<ListboxNavigation<unknown>['optionProps']>;
|
|
86
|
+
icon?: ReactNode;
|
|
87
|
+
label: ReactNode;
|
|
88
|
+
/** Under the label, or beside it with `inline`. */
|
|
89
|
+
description?: ReactNode;
|
|
90
|
+
/** The description on the label's line, as a command's summary beside its name. */
|
|
91
|
+
inline?: boolean | undefined;
|
|
92
|
+
/** Short muted text at the row's end, such as a key. */
|
|
93
|
+
hint?: ReactNode;
|
|
94
|
+
className?: string | undefined;
|
|
95
|
+
}
|
|
96
|
+
/** One option of a ListboxPanel: an icon, its label and description, and a hint at its end. */
|
|
97
|
+
export declare function ListboxOption({ option, icon, label, description, inline, hint, className, }: ListboxOptionProps): import("react").JSX.Element;
|
|
98
|
+
/** Options under a heading in a ListboxPanel. */
|
|
99
|
+
export declare function ListboxGroup({ label, children }: {
|
|
100
|
+
label: ReactNode;
|
|
101
|
+
children: ReactNode;
|
|
102
|
+
}): import("react").JSX.Element;
|
|
103
|
+
export {};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { cx } from "../cx.js";
|
|
2
|
+
import { panelClassName, panelItemClassName } from "./floating.js";
|
|
3
|
+
import { Popover } from "./Popover.js";
|
|
4
|
+
import "./comboboxItems.js";
|
|
5
|
+
import { useEffect, useId, useState } from "react";
|
|
6
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
//#region src/components/Listbox.tsx
|
|
8
|
+
/** The parts a highlighted list needs while focus stays on the box the reader types in, such
|
|
9
|
+
* as a text box, an editor or a search input: the highlight and its keys, and the ids that
|
|
10
|
+
* let assistive technology follow the highlight. Spread `hostProps` on the focused box,
|
|
11
|
+
* `listProps` on the list and `optionProps(i)` on each option, or draw them with
|
|
12
|
+
* ListboxPanel and ListboxOption. */
|
|
13
|
+
function useListboxNavigation({ items, onChoose, onDismiss, open = true, loop = true, chooseKeys = ["Tab"], homeEnd = false, isDisabled }) {
|
|
14
|
+
const listId = useId();
|
|
15
|
+
const [active, setActive] = useState(0);
|
|
16
|
+
const count = items.length;
|
|
17
|
+
const shown = count === 0 ? -1 : Math.min(Math.max(active, 0), count - 1);
|
|
18
|
+
const shownOpen = open && count > 0;
|
|
19
|
+
const optionId = (index) => `${listId}-${index}`;
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!shownOpen || shown < 0) return;
|
|
22
|
+
document.getElementById(`${listId}-${shown}`)?.scrollIntoView?.({ block: "nearest" });
|
|
23
|
+
}, [
|
|
24
|
+
shownOpen,
|
|
25
|
+
shown,
|
|
26
|
+
listId
|
|
27
|
+
]);
|
|
28
|
+
const step = (from, by) => {
|
|
29
|
+
for (let moved = 1; moved <= count; moved++) {
|
|
30
|
+
let next = from + by * moved;
|
|
31
|
+
if (next < 0 || next >= count) {
|
|
32
|
+
if (!loop) return from;
|
|
33
|
+
next = (next + count) % count;
|
|
34
|
+
}
|
|
35
|
+
const item = items[next];
|
|
36
|
+
if (item !== void 0 && !isDisabled?.(item)) return next;
|
|
37
|
+
}
|
|
38
|
+
return from;
|
|
39
|
+
};
|
|
40
|
+
const choose = (index) => {
|
|
41
|
+
const item = items[index];
|
|
42
|
+
if (item !== void 0 && !isDisabled?.(item)) onChoose(item, index);
|
|
43
|
+
};
|
|
44
|
+
/** Handles a key the list owns, and says whether it did; leave the rest to the host. */
|
|
45
|
+
const handleKey = (event) => {
|
|
46
|
+
const composing = "nativeEvent" in event ? event.nativeEvent.isComposing : event.isComposing;
|
|
47
|
+
if (!shownOpen || composing) return false;
|
|
48
|
+
const ctrlOnly = event.ctrlKey && !event.metaKey && !event.altKey;
|
|
49
|
+
switch (true) {
|
|
50
|
+
case event.key === "ArrowDown" || ctrlOnly && event.key === "n":
|
|
51
|
+
setActive(step(shown, 1));
|
|
52
|
+
break;
|
|
53
|
+
case event.key === "ArrowUp" || ctrlOnly && event.key === "p":
|
|
54
|
+
setActive(step(shown, -1));
|
|
55
|
+
break;
|
|
56
|
+
case homeEnd && event.key === "Home":
|
|
57
|
+
setActive(step(-1, 1));
|
|
58
|
+
break;
|
|
59
|
+
case homeEnd && event.key === "End":
|
|
60
|
+
setActive(step(count, -1));
|
|
61
|
+
break;
|
|
62
|
+
case event.key === "Escape":
|
|
63
|
+
if (!onDismiss) return false;
|
|
64
|
+
onDismiss();
|
|
65
|
+
break;
|
|
66
|
+
case event.key === "Enter" || chooseKeys.includes(event.key):
|
|
67
|
+
if (event.shiftKey || event.altKey || event.metaKey || event.ctrlKey) return false;
|
|
68
|
+
choose(shown);
|
|
69
|
+
break;
|
|
70
|
+
default: return false;
|
|
71
|
+
}
|
|
72
|
+
event.preventDefault();
|
|
73
|
+
return true;
|
|
74
|
+
};
|
|
75
|
+
return {
|
|
76
|
+
/** The highlighted item's index, or -1 with nothing to show. */
|
|
77
|
+
active: shown,
|
|
78
|
+
setActive,
|
|
79
|
+
handleKey,
|
|
80
|
+
listId,
|
|
81
|
+
/** For the box that keeps focus: it points at the list and its highlighted option. */
|
|
82
|
+
hostProps: {
|
|
83
|
+
"aria-autocomplete": "list",
|
|
84
|
+
"aria-controls": shownOpen ? listId : void 0,
|
|
85
|
+
"aria-activedescendant": shownOpen && shown >= 0 ? optionId(shown) : void 0
|
|
86
|
+
},
|
|
87
|
+
listProps: {
|
|
88
|
+
id: listId,
|
|
89
|
+
role: "listbox"
|
|
90
|
+
},
|
|
91
|
+
optionProps: (index) => {
|
|
92
|
+
const item = items[index];
|
|
93
|
+
const disabled = item !== void 0 && isDisabled?.(item) === true;
|
|
94
|
+
return {
|
|
95
|
+
id: optionId(index),
|
|
96
|
+
role: "option",
|
|
97
|
+
tabIndex: -1,
|
|
98
|
+
"aria-selected": index === shown,
|
|
99
|
+
"aria-disabled": disabled || void 0,
|
|
100
|
+
"data-highlighted": index === shown || void 0,
|
|
101
|
+
"data-disabled": disabled || void 0,
|
|
102
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
103
|
+
onMouseMove: () => {
|
|
104
|
+
if (index !== shown && !disabled) setActive(index);
|
|
105
|
+
},
|
|
106
|
+
onClick: () => choose(index)
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/** The panel of a highlighted list that the reader drives from a box that keeps focus. */
|
|
112
|
+
function ListboxPanel({ listProps, "aria-label": label, footer, empty, at, onDismiss, className, children }) {
|
|
113
|
+
const hasOptions = Array.isArray(children) ? children.some(Boolean) : Boolean(children);
|
|
114
|
+
const body = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
|
|
115
|
+
id: listProps.id,
|
|
116
|
+
role: "listbox",
|
|
117
|
+
"aria-label": label,
|
|
118
|
+
className: cx("max-h-96 overflow-y-auto overscroll-contain", "p-1"),
|
|
119
|
+
children: hasOptions ? children : empty ? /* @__PURE__ */ jsx("div", {
|
|
120
|
+
className: "px-2 py-1.5 text-(--theme-muted-text-color)",
|
|
121
|
+
children: empty
|
|
122
|
+
}) : null
|
|
123
|
+
}), footer && /* @__PURE__ */ jsx("div", {
|
|
124
|
+
className: "border-t border-(--theme-border) px-3 py-1.5 text-xs text-(--theme-muted-text-color)",
|
|
125
|
+
children: footer
|
|
126
|
+
})] });
|
|
127
|
+
if (at) return /* @__PURE__ */ jsx(Popover, {
|
|
128
|
+
at,
|
|
129
|
+
open: true,
|
|
130
|
+
onOpenChange: (next) => {
|
|
131
|
+
if (!next) onDismiss?.();
|
|
132
|
+
},
|
|
133
|
+
side: "bottom",
|
|
134
|
+
align: "start",
|
|
135
|
+
initialFocus: "none",
|
|
136
|
+
finalFocus: false,
|
|
137
|
+
"aria-label": label,
|
|
138
|
+
className: cx("w-72 overflow-hidden p-0", className),
|
|
139
|
+
children: body
|
|
140
|
+
});
|
|
141
|
+
return /* @__PURE__ */ jsx("div", {
|
|
142
|
+
className: cx("overflow-hidden", panelClassName, className),
|
|
143
|
+
children: body
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
/** One option of a ListboxPanel: an icon, its label and description, and a hint at its end. */
|
|
147
|
+
function ListboxOption({ option, icon, label, description, inline = false, hint, className }) {
|
|
148
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
149
|
+
...option,
|
|
150
|
+
className: cx(panelItemClassName, inline && "items-baseline", className),
|
|
151
|
+
children: [
|
|
152
|
+
icon && /* @__PURE__ */ jsx("span", {
|
|
153
|
+
className: "flex shrink-0 items-center self-center",
|
|
154
|
+
children: icon
|
|
155
|
+
}),
|
|
156
|
+
/* @__PURE__ */ jsxs("span", {
|
|
157
|
+
className: cx("flex min-w-0", inline ? "items-baseline gap-3" : "flex-col"),
|
|
158
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
159
|
+
className: "truncate",
|
|
160
|
+
children: label
|
|
161
|
+
}), description && /* @__PURE__ */ jsx("span", {
|
|
162
|
+
className: cx("text-xs text-(--theme-muted-text-color)", inline ? "truncate" : "leading-snug"),
|
|
163
|
+
children: description
|
|
164
|
+
})]
|
|
165
|
+
}),
|
|
166
|
+
hint && /* @__PURE__ */ jsx("span", {
|
|
167
|
+
className: "ml-auto flex h-[1lh] shrink-0 items-center gap-2 self-start pl-2",
|
|
168
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
169
|
+
className: "text-xs tabular-nums text-(--theme-muted-text-color)",
|
|
170
|
+
children: hint
|
|
171
|
+
})
|
|
172
|
+
})
|
|
173
|
+
]
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
/** Options under a heading in a ListboxPanel. */
|
|
177
|
+
function ListboxGroup({ label, children }) {
|
|
178
|
+
const id = useId();
|
|
179
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
180
|
+
role: "group",
|
|
181
|
+
"aria-labelledby": id,
|
|
182
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
183
|
+
id,
|
|
184
|
+
className: "px-2 pt-2 pb-1 text-xs text-(--theme-muted-text-color)",
|
|
185
|
+
children: label
|
|
186
|
+
}), children]
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
//#endregion
|
|
190
|
+
export { ListboxGroup, ListboxOption, ListboxPanel, useListboxNavigation };
|