@parallelworks/ui 0.42.0 → 0.44.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/dist/components/Combobox.d.ts +2 -19
- package/dist/components/Combobox.js +33 -21
- package/dist/components/CreateModal.d.ts +3 -3
- package/dist/components/CreateModal.js +40 -29
- package/dist/components/Select.d.ts +4 -1
- package/dist/components/Select.js +6 -1
- package/dist/components/comboboxItems.d.ts +24 -0
- package/dist/components/comboboxItems.js +10 -0
- package/dist/form/Fields/DropdownField.js +1 -1
- package/dist/form/Fields/MultiDropdownField.js +1 -1
- package/dist/form/lib.js +3 -2
- package/dist/form/useChoiceField.d.ts +3 -5
- package/dist/form/useChoiceField.js +1 -4
- package/dist/form/utils/fieldItems.d.ts +29 -7
- package/dist/form/utils/fieldItems.js +28 -7
- package/dist/list/rowMenu.d.ts +7 -2
- package/dist/list/rowMenu.js +6 -4
- package/package.json +1 -1
|
@@ -1,22 +1,6 @@
|
|
|
1
1
|
import { type ReactElement, type ReactNode } from 'react';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
label: string;
|
|
5
|
-
description?: string | undefined;
|
|
6
|
-
icon?: ReactNode;
|
|
7
|
-
disabled?: boolean | undefined;
|
|
8
|
-
}
|
|
9
|
-
export interface ComboboxGroup<Value = string> {
|
|
10
|
-
label: string;
|
|
11
|
-
/** Leads the heading, such as a provider's logo. */
|
|
12
|
-
icon?: ReactNode;
|
|
13
|
-
/** Follows the label, such as who shared the group or a status like "Not responding". */
|
|
14
|
-
description?: string | undefined;
|
|
15
|
-
/** Empty for a group that only reports a status; a search finds it by its heading. */
|
|
16
|
-
items: readonly (ComboboxItem<Value> | (Value & string))[];
|
|
17
|
-
}
|
|
18
|
-
/** A flat list, or groups under headings. A bare string is an item labelled by its value. */
|
|
19
|
-
export type ComboboxItems<Value = string> = readonly (ComboboxItem<Value> | (Value & string))[] | readonly ComboboxGroup<Value>[];
|
|
2
|
+
import { type ComboboxItems } from './comboboxItems.js';
|
|
3
|
+
export type { ComboboxGroup, ComboboxItem, ComboboxItems, ComboboxLeaf, } from './comboboxItems.js';
|
|
20
4
|
interface FieldProps {
|
|
21
5
|
id?: string | undefined;
|
|
22
6
|
placeholder?: string | undefined;
|
|
@@ -63,4 +47,3 @@ export interface AutocompleteProps extends FieldProps {
|
|
|
63
47
|
export declare function Combobox<Value = string>(props: ComboboxProps<Value>): import("react").JSX.Element;
|
|
64
48
|
/** A free-text field that suggests values as you type. */
|
|
65
49
|
export declare function Autocomplete({ items, value, defaultValue, onValueChange, className, placeholder, disabled, required, id, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, 'aria-invalid': ariaInvalid, }: AutocompleteProps): import("react").JSX.Element;
|
|
66
|
-
export {};
|
|
@@ -5,6 +5,7 @@ import { panelClassName, panelItemClassName, portalContainer, positionerClassNam
|
|
|
5
5
|
import { Tooltip } from "./Tooltip.js";
|
|
6
6
|
import { useStrings } from "./Provider.js";
|
|
7
7
|
import { useQueryMatch } from "./search.js";
|
|
8
|
+
import { isComboboxGroup } from "./comboboxItems.js";
|
|
8
9
|
import { clsx } from "clsx";
|
|
9
10
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
10
11
|
import { useCallback, useImperativeHandle, useMemo, useRef, useState } from "react";
|
|
@@ -13,9 +14,6 @@ import { Autocomplete } from "@base-ui/react/autocomplete";
|
|
|
13
14
|
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
14
15
|
import { dequal } from "dequal";
|
|
15
16
|
//#region src/components/Combobox.tsx
|
|
16
|
-
function isGroup(item) {
|
|
17
|
-
return typeof item === "object" && "items" in item;
|
|
18
|
-
}
|
|
19
17
|
function toEntry(item, group) {
|
|
20
18
|
const entry = typeof item === "string" ? {
|
|
21
19
|
value: item,
|
|
@@ -26,29 +24,43 @@ function toEntry(item, group) {
|
|
|
26
24
|
group
|
|
27
25
|
};
|
|
28
26
|
}
|
|
29
|
-
/** Base UI takes either flat items or groups, and filters and navigates by the leaf items
|
|
27
|
+
/** Base UI takes either flat items or groups, and filters and navigates by the leaf items, so
|
|
28
|
+
* items listed between groups gather into headless groups where they sit. */
|
|
30
29
|
function useEntries(items) {
|
|
31
30
|
return useMemo(() => {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const groups = items.map((g, order) => ({
|
|
35
|
-
label: g.label,
|
|
36
|
-
icon: g.icon,
|
|
37
|
-
description: g.description,
|
|
38
|
-
items: g.items.map((item) => toEntry(item, g.label)),
|
|
39
|
-
order
|
|
40
|
-
}));
|
|
31
|
+
if (!items.some(isComboboxGroup)) {
|
|
32
|
+
const flat = items.map((item) => toEntry(item));
|
|
41
33
|
return {
|
|
42
|
-
items:
|
|
43
|
-
flat
|
|
44
|
-
statusGroups:
|
|
34
|
+
items: flat,
|
|
35
|
+
flat,
|
|
36
|
+
statusGroups: []
|
|
45
37
|
};
|
|
46
38
|
}
|
|
47
|
-
const
|
|
39
|
+
const groups = [];
|
|
40
|
+
for (const item of items) {
|
|
41
|
+
if (isComboboxGroup(item)) {
|
|
42
|
+
groups.push({
|
|
43
|
+
label: item.label,
|
|
44
|
+
icon: item.icon,
|
|
45
|
+
description: item.description,
|
|
46
|
+
items: item.items.map((leaf) => toEntry(leaf, item.label)),
|
|
47
|
+
order: groups.length
|
|
48
|
+
});
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const last = groups.at(-1);
|
|
52
|
+
if (last?.headless) last.items.push(toEntry(item));
|
|
53
|
+
else groups.push({
|
|
54
|
+
label: "",
|
|
55
|
+
items: [toEntry(item)],
|
|
56
|
+
order: groups.length,
|
|
57
|
+
headless: true
|
|
58
|
+
});
|
|
59
|
+
}
|
|
48
60
|
return {
|
|
49
|
-
items:
|
|
50
|
-
flat,
|
|
51
|
-
statusGroups:
|
|
61
|
+
items: groups,
|
|
62
|
+
flat: groups.flatMap((g) => g.items),
|
|
63
|
+
statusGroups: groups.filter((g) => g.items.length === 0)
|
|
52
64
|
};
|
|
53
65
|
}, [items]);
|
|
54
66
|
}
|
|
@@ -294,7 +306,7 @@ function toRows(filtered, statusGroups) {
|
|
|
294
306
|
const rowOfItem = [];
|
|
295
307
|
for (const node of nodes) {
|
|
296
308
|
const leaves = "items" in node ? node.items : [node];
|
|
297
|
-
if ("items" in node) rows.push({
|
|
309
|
+
if ("items" in node && !node.headless) rows.push({
|
|
298
310
|
kind: "group",
|
|
299
311
|
group: node
|
|
300
312
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { InputHTMLAttributes, ReactNode, TextareaHTMLAttributes } from 'react';
|
|
2
|
-
import { type ComboboxItems } from './
|
|
2
|
+
import { type ComboboxItems } from './comboboxItems.js';
|
|
3
3
|
import type { MenuEntry } from './MenuEntries.js';
|
|
4
4
|
interface CreateModalProps {
|
|
5
5
|
open: boolean;
|
|
@@ -38,13 +38,13 @@ interface CreateModalProps {
|
|
|
38
38
|
/** Submits on Enter in the name field or ⌘/Ctrl+Enter from anywhere in the panel. */
|
|
39
39
|
export declare function CreateModal({ open, onClose, typeIcon, typeLabel, typeMenu, namePlaceholder, hideName, initialName, showDescription, descriptionPlaceholder, submitLabel, submittingLabel, validateName, sanitizeName, nameHint, children, properties, advanced, submitDisabled, hideSubmit, nameOptional, onNameChange, dirty, showFavorite, onSubmit, }: CreateModalProps): import("react").JSX.Element;
|
|
40
40
|
/** Labeled single-line input row for the dialog's extra fields. */
|
|
41
|
-
export declare function CreateModalField({ label, tooltip, ...props }: InputHTMLAttributes<HTMLInputElement> & {
|
|
41
|
+
export declare function CreateModalField({ label, tooltip, id, ...props }: InputHTMLAttributes<HTMLInputElement> & {
|
|
42
42
|
label: string;
|
|
43
43
|
/** Help text surfaced via an info icon next to the label. */
|
|
44
44
|
tooltip?: string | undefined;
|
|
45
45
|
}): import("react").JSX.Element;
|
|
46
46
|
/** Labeled multi-line field for credential blobs (JSON keys, PEM keys). */
|
|
47
|
-
export declare function CreateModalTextarea({ label, tooltip, rows, ...props }: TextareaHTMLAttributes<HTMLTextAreaElement> & {
|
|
47
|
+
export declare function CreateModalTextarea({ label, tooltip, rows, id, ...props }: TextareaHTMLAttributes<HTMLTextAreaElement> & {
|
|
48
48
|
label: string;
|
|
49
49
|
/** Help text surfaced via an info icon next to the label. */
|
|
50
50
|
tooltip?: string | undefined;
|
|
@@ -5,6 +5,7 @@ import { LuChevronRight } from "../icons/lu/LuChevronRight.js";
|
|
|
5
5
|
import { Tooltip } from "./Tooltip.js";
|
|
6
6
|
import { useStrings } from "./Provider.js";
|
|
7
7
|
import { ActionMenu } from "./ActionMenu.js";
|
|
8
|
+
import { comboboxLeaves } from "./comboboxItems.js";
|
|
8
9
|
import { Autocomplete, Combobox } from "./Combobox.js";
|
|
9
10
|
import { ghostButtonClasses } from "./ghostButton.js";
|
|
10
11
|
import { Modal } from "./Modal.js";
|
|
@@ -199,34 +200,53 @@ function CreateModal({ open, onClose, typeIcon, typeLabel, typeMenu, namePlaceho
|
|
|
199
200
|
}
|
|
200
201
|
var fieldInputClasses = "w-full h-8 px-2.5 rounded-md border border-(--theme-border) bg-(--theme-input-bg) shadow-none text-sm text-(--theme-app) placeholder:text-(--theme-muted-text-color) transition-colors focus:outline-none focus:ring-0 focus:border-(--theme-element)";
|
|
201
202
|
var fieldLabelClasses = "block mb-1 text-xs font-medium text-(--theme-muted-text-color)";
|
|
203
|
+
/** A field's label, with its help beside it rather than inside the label, so
|
|
204
|
+
* the label names the field and not the help button. */
|
|
205
|
+
function FieldLabel({ id, label, tooltip }) {
|
|
206
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
207
|
+
className: clsx(fieldLabelClasses, "flex items-center gap-1"),
|
|
208
|
+
children: [/* @__PURE__ */ jsx("label", {
|
|
209
|
+
htmlFor: id,
|
|
210
|
+
children: label
|
|
211
|
+
}), tooltip && /* @__PURE__ */ jsx(InfoTip, { text: tooltip })]
|
|
212
|
+
});
|
|
213
|
+
}
|
|
202
214
|
/** Labeled single-line input row for the dialog's extra fields. */
|
|
203
|
-
function CreateModalField({ label, tooltip, ...props }) {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
215
|
+
function CreateModalField({ label, tooltip, id, ...props }) {
|
|
216
|
+
const fallbackId = useId();
|
|
217
|
+
const fieldId = id ?? fallbackId;
|
|
218
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
219
|
+
className: "mb-3",
|
|
220
|
+
children: [/* @__PURE__ */ jsx(FieldLabel, {
|
|
221
|
+
id: fieldId,
|
|
222
|
+
label,
|
|
223
|
+
tooltip
|
|
209
224
|
}), /* @__PURE__ */ jsx("input", {
|
|
210
225
|
type: "text",
|
|
211
226
|
autoComplete: "off",
|
|
212
227
|
"data-1p-ignore": true,
|
|
213
228
|
...props,
|
|
229
|
+
id: fieldId,
|
|
214
230
|
className: fieldInputClasses
|
|
215
231
|
})]
|
|
216
232
|
});
|
|
217
233
|
}
|
|
218
234
|
/** Labeled multi-line field for credential blobs (JSON keys, PEM keys). */
|
|
219
|
-
function CreateModalTextarea({ label, tooltip, rows = 4, ...props }) {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
235
|
+
function CreateModalTextarea({ label, tooltip, rows = 4, id, ...props }) {
|
|
236
|
+
const fallbackId = useId();
|
|
237
|
+
const fieldId = id ?? fallbackId;
|
|
238
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
239
|
+
className: "mb-3",
|
|
240
|
+
children: [/* @__PURE__ */ jsx(FieldLabel, {
|
|
241
|
+
id: fieldId,
|
|
242
|
+
label,
|
|
243
|
+
tooltip
|
|
225
244
|
}), /* @__PURE__ */ jsx("textarea", {
|
|
226
245
|
autoComplete: "off",
|
|
227
246
|
"data-1p-ignore": true,
|
|
228
247
|
rows,
|
|
229
248
|
...props,
|
|
249
|
+
id: fieldId,
|
|
230
250
|
className: clsx(fieldInputClasses, "h-auto py-2 font-mono text-xs leading-relaxed resize-y")
|
|
231
251
|
})]
|
|
232
252
|
});
|
|
@@ -236,12 +256,10 @@ function CreateModalAutocompleteField({ label, tooltip, value, onValueChange, it
|
|
|
236
256
|
const id = useId();
|
|
237
257
|
return /* @__PURE__ */ jsxs("div", {
|
|
238
258
|
className: "mb-3",
|
|
239
|
-
children: [/* @__PURE__ */
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
children: label
|
|
244
|
-
}), tooltip && /* @__PURE__ */ jsx(InfoTip, { text: tooltip })]
|
|
259
|
+
children: [/* @__PURE__ */ jsx(FieldLabel, {
|
|
260
|
+
id,
|
|
261
|
+
label,
|
|
262
|
+
tooltip
|
|
245
263
|
}), /* @__PURE__ */ jsx(Autocomplete, {
|
|
246
264
|
id,
|
|
247
265
|
items,
|
|
@@ -270,7 +288,10 @@ function CreateModalSwitch({ label, checked, onCheckedChange, tooltip }) {
|
|
|
270
288
|
* icons, descriptions and group headings (e.g. Clusters vs Kubernetes). */
|
|
271
289
|
function CreateModalSelect({ label, icon, value, items, onValueChange, disabled = false, disabledReason, tooltip }) {
|
|
272
290
|
const t = useStrings().modal;
|
|
273
|
-
const selected =
|
|
291
|
+
const selected = comboboxLeaves(items).map((item) => typeof item === "string" ? {
|
|
292
|
+
value: item,
|
|
293
|
+
label: item
|
|
294
|
+
} : item).find((item) => item.value === value);
|
|
274
295
|
const selectedLabel = selected?.label ?? (value || void 0);
|
|
275
296
|
const pillContent = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
276
297
|
selected?.icon ?? icon,
|
|
@@ -307,15 +328,5 @@ function CreateModalSelect({ label, icon, value, items, onValueChange, disabled
|
|
|
307
328
|
})
|
|
308
329
|
});
|
|
309
330
|
}
|
|
310
|
-
function leafItems(items) {
|
|
311
|
-
return items.flatMap((node) => {
|
|
312
|
-
if (typeof node === "string") return [{
|
|
313
|
-
value: node,
|
|
314
|
-
label: node
|
|
315
|
-
}];
|
|
316
|
-
if ("items" in node) return leafItems(node.items);
|
|
317
|
-
return [node];
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
331
|
//#endregion
|
|
321
332
|
export { CreateModal, CreateModalAutocompleteField, CreateModalField, CreateModalSelect, CreateModalSwitch, CreateModalTextarea };
|
|
@@ -21,7 +21,10 @@ export type SelectProps<Value, Multiple extends boolean | undefined = false> = O
|
|
|
21
21
|
trigger?: ReactElement | undefined;
|
|
22
22
|
/** A hint on the trigger button. */
|
|
23
23
|
triggerTooltip?: ReactNode;
|
|
24
|
+
/** The item a person is on as they move through the open list, then undefined once it
|
|
25
|
+
* closes, such as to preview a theme before it is chosen. */
|
|
26
|
+
onItemHighlighted?: ((value: Value | undefined) => void) | undefined;
|
|
24
27
|
};
|
|
25
28
|
/** Picks one value, or several with `multiple`, from a short list. Use
|
|
26
29
|
* Combobox when the list is long enough to need filtering. */
|
|
27
|
-
export declare function Select<Value, Multiple extends boolean | undefined = false>({ items, placeholder, className, 'aria-label': ariaLabel, 'aria-describedby': describedBy, 'aria-invalid': invalid, trigger, triggerTooltip, ...rootProps }: SelectProps<Value, Multiple>): import("react").JSX.Element;
|
|
30
|
+
export declare function Select<Value, Multiple extends boolean | undefined = false>({ items, placeholder, className, 'aria-label': ariaLabel, 'aria-describedby': describedBy, 'aria-invalid': invalid, trigger, triggerTooltip, onItemHighlighted, ...rootProps }: SelectProps<Value, Multiple>): import("react").JSX.Element;
|
|
@@ -9,7 +9,7 @@ import { Select } from "@base-ui/react/select";
|
|
|
9
9
|
var triggerClassName = clsx("flex w-full items-center justify-between gap-2 rounded-md border border-(--theme-border) bg-(--theme-input-bg) py-2 pl-3 pr-2 text-left text-sm text-(--theme-app) cursor-pointer outline-none", "focus-visible:border-(--theme-element) focus-visible:ring-1 focus-visible:ring-(--theme-element)", "aria-invalid:border-(--theme-error) data-disabled:cursor-not-allowed data-disabled:bg-(--theme-input-disabled-bg) data-disabled:text-(--theme-input-disabled-text)");
|
|
10
10
|
/** Picks one value, or several with `multiple`, from a short list. Use
|
|
11
11
|
* Combobox when the list is long enough to need filtering. */
|
|
12
|
-
function Select$1({ items, placeholder, className, "aria-label": ariaLabel, "aria-describedby": describedBy, "aria-invalid": invalid, trigger, triggerTooltip, ...rootProps }) {
|
|
12
|
+
function Select$1({ items, placeholder, className, "aria-label": ariaLabel, "aria-describedby": describedBy, "aria-invalid": invalid, trigger, triggerTooltip, onItemHighlighted, ...rootProps }) {
|
|
13
13
|
const labelOf = (value) => items.find((item) => Object.is(item.value, value))?.label;
|
|
14
14
|
const triggerProps = {
|
|
15
15
|
"aria-label": ariaLabel,
|
|
@@ -19,6 +19,10 @@ function Select$1({ items, placeholder, className, "aria-label": ariaLabel, "ari
|
|
|
19
19
|
return /* @__PURE__ */ jsxs(Select.Root, {
|
|
20
20
|
items,
|
|
21
21
|
...rootProps,
|
|
22
|
+
onOpenChange: (open, details) => {
|
|
23
|
+
rootProps.onOpenChange?.(open, details);
|
|
24
|
+
if (!open) onItemHighlighted?.(void 0);
|
|
25
|
+
},
|
|
22
26
|
children: [trigger ? /* @__PURE__ */ jsx(Tooltip, {
|
|
23
27
|
content: triggerTooltip,
|
|
24
28
|
children: /* @__PURE__ */ jsx(Select.Trigger, {
|
|
@@ -50,6 +54,7 @@ function Select$1({ items, placeholder, className, "aria-label": ariaLabel, "ari
|
|
|
50
54
|
value: item.value,
|
|
51
55
|
disabled: item.disabled,
|
|
52
56
|
className: clsx("grid grid-cols-[1rem_1fr]", panelItemClassName),
|
|
57
|
+
onFocus: onItemHighlighted && (() => onItemHighlighted(item.value)),
|
|
53
58
|
children: [
|
|
54
59
|
/* @__PURE__ */ jsx(Select.ItemIndicator, {
|
|
55
60
|
className: "col-start-1",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export interface ComboboxItem<Value = string> {
|
|
3
|
+
value: Value;
|
|
4
|
+
label: string;
|
|
5
|
+
description?: string | undefined;
|
|
6
|
+
icon?: ReactNode;
|
|
7
|
+
disabled?: boolean | undefined;
|
|
8
|
+
}
|
|
9
|
+
export interface ComboboxGroup<Value = string> {
|
|
10
|
+
label: string;
|
|
11
|
+
/** Leads the heading, such as a provider's logo. */
|
|
12
|
+
icon?: ReactNode;
|
|
13
|
+
/** Follows the label, such as who shared the group or a status like "Not responding". */
|
|
14
|
+
description?: string | undefined;
|
|
15
|
+
/** Empty for a group that only reports a status; a search finds it by its heading. */
|
|
16
|
+
items: readonly ComboboxLeaf<Value>[];
|
|
17
|
+
}
|
|
18
|
+
/** One item: an option, or a bare string labelled by its value. */
|
|
19
|
+
export type ComboboxLeaf<Value = string> = ComboboxItem<Value> | (Value & string);
|
|
20
|
+
/** Items, groups under headings, or both: items between groups list where they sit, without a heading. */
|
|
21
|
+
export type ComboboxItems<Value = string> = readonly (ComboboxLeaf<Value> | ComboboxGroup<Value>)[];
|
|
22
|
+
export declare function isComboboxGroup<Value>(item: ComboboxLeaf<Value> | ComboboxGroup<Value>): item is ComboboxGroup<Value>;
|
|
23
|
+
/** Every item, out of their groups. */
|
|
24
|
+
export declare function comboboxLeaves<Value>(items: ComboboxItems<Value>): ComboboxLeaf<Value>[];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/components/comboboxItems.ts
|
|
2
|
+
function isComboboxGroup(item) {
|
|
3
|
+
return typeof item === "object" && "items" in item;
|
|
4
|
+
}
|
|
5
|
+
/** Every item, out of their groups. */
|
|
6
|
+
function comboboxLeaves(items) {
|
|
7
|
+
return items.flatMap((item) => isComboboxGroup(item) ? item.items : [item]);
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { comboboxLeaves, isComboboxGroup };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Autocomplete, Combobox } from "../../components/Combobox.js";
|
|
2
|
+
import { toFieldItems } from "../utils/fieldItems.js";
|
|
2
3
|
import { FieldWrapper } from "../FieldWrapper.js";
|
|
3
4
|
import { useChoiceField } from "../useChoiceField.js";
|
|
4
5
|
import { useDependentOptions } from "../utils/dependentOptions.js";
|
|
5
|
-
import { toFieldItems } from "../utils/fieldItems.js";
|
|
6
6
|
import { getPlaceholder } from "../utils/getPlaceholder.js";
|
|
7
7
|
import { jsx } from "react/jsx-runtime";
|
|
8
8
|
import { useMemo } from "react";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Combobox } from "../../components/Combobox.js";
|
|
2
|
+
import { toFieldItems } from "../utils/fieldItems.js";
|
|
2
3
|
import { FieldWrapper } from "../FieldWrapper.js";
|
|
3
4
|
import { useFieldValue } from "../useFieldValue.js";
|
|
4
5
|
import { useDependentOptions } from "../utils/dependentOptions.js";
|
|
5
|
-
import { toFieldItems } from "../utils/fieldItems.js";
|
|
6
6
|
import { usePrevious } from "../usePrevious.js";
|
|
7
7
|
import { jsx } from "react/jsx-runtime";
|
|
8
8
|
import { useEffect, useMemo } from "react";
|
package/dist/form/lib.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { findChoice, toFieldItems } from "./utils/fieldItems.js";
|
|
2
|
+
import { applySecondaryField } from "./utils/secondaryField.js";
|
|
2
3
|
import { copyBounds } from "./Wizard/utils.js";
|
|
3
4
|
//#region src/form/lib.ts
|
|
4
5
|
var EMPTY_ENTRY = {};
|
|
@@ -107,7 +108,7 @@ function initializeValues(options, srcData = {}, blankDefault) {
|
|
|
107
108
|
if (data[field] !== void 0) {
|
|
108
109
|
acc[field] = data[field];
|
|
109
110
|
if ((fieldSchema.type === "dropdown" || fieldSchema.type === "storage") && fieldSchema.secondaryField && Array.isArray(fieldSchema.options)) {
|
|
110
|
-
const option =
|
|
111
|
+
const option = findChoice(toFieldItems(fieldSchema.options), data[field]);
|
|
111
112
|
applySecondaryField(fieldSchema.secondaryField, option?.secondaryValue, (secondaryField, secondaryValue) => {
|
|
112
113
|
if (!(fieldSchema.type === "storage" && Object.hasOwn(options, secondaryField) && source[secondaryField] !== void 0)) impureSetValueFromPath(acc, secondaryField, secondaryValue);
|
|
113
114
|
});
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
export type ChoiceItem
|
|
4
|
-
secondaryValue?: unknown;
|
|
5
|
-
};
|
|
1
|
+
import type { ComboboxItems } from '../components/Combobox.js';
|
|
2
|
+
import { type ChoiceItem } from './utils/fieldItems.js';
|
|
3
|
+
export type { ChoiceItem };
|
|
6
4
|
/**
|
|
7
5
|
* A field that picks one of `items`: its value, and the choice the person makes, which also sets the
|
|
8
6
|
* fields its `secondaryField` names from the item's `secondaryValue`.
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
+
import { itemValue, leaves } from "./utils/fieldItems.js";
|
|
1
2
|
import { applySecondaryField } from "./utils/secondaryField.js";
|
|
2
3
|
import { useFieldValue } from "./useFieldValue.js";
|
|
3
4
|
import { useEffect, useEffectEvent, useRef } from "react";
|
|
4
5
|
import { dequal } from "dequal";
|
|
5
6
|
import { useFormContext } from "react-hook-form";
|
|
6
7
|
//#region src/form/useChoiceField.ts
|
|
7
|
-
function leaves(items) {
|
|
8
|
-
return items.flatMap((item) => typeof item === "object" && "items" in item ? item.items : [item]);
|
|
9
|
-
}
|
|
10
|
-
var itemValue = (item) => typeof item === "string" ? item : item.value;
|
|
11
8
|
/**
|
|
12
9
|
* A field that picks one of `items`: its value, and the choice the person makes, which also sets the
|
|
13
10
|
* fields its `secondaryField` names from the item's `secondaryValue`.
|
|
@@ -1,10 +1,32 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { type ComboboxItem, type ComboboxItems } from '../../components/comboboxItems.js';
|
|
3
|
+
/** A choice as workflow YAML writes it, or as a host's option hook builds one. */
|
|
4
|
+
export interface FieldOptionItem<Value = string> {
|
|
4
5
|
label: string;
|
|
5
6
|
value: Value;
|
|
6
|
-
description?: string;
|
|
7
|
-
|
|
7
|
+
description?: string | undefined;
|
|
8
|
+
/** The older key for description. */
|
|
9
|
+
secondaryLabel?: string | undefined;
|
|
10
|
+
/** What choosing this sets the field's `secondaryField` to. */
|
|
11
|
+
secondaryValue?: unknown;
|
|
12
|
+
disabled?: boolean | undefined;
|
|
13
|
+
icon?: ReactNode;
|
|
14
|
+
}
|
|
15
|
+
/** Choices under a heading. */
|
|
16
|
+
export interface FieldOptionGroup<Value = string> {
|
|
17
|
+
category?: string | undefined;
|
|
18
|
+
options: readonly (FieldOptionItem<Value> | string)[];
|
|
19
|
+
}
|
|
20
|
+
/** A dropdown option: a bare string is a choice labelled by its value. */
|
|
21
|
+
export type FieldOption<Value = string> = string | FieldOptionItem<Value> | FieldOptionGroup<Value>;
|
|
22
|
+
/** A picker item that also sets other fields when chosen, through `secondaryField`. */
|
|
23
|
+
export type ChoiceItem<Value = unknown> = ComboboxItem<Value> & {
|
|
24
|
+
secondaryValue?: unknown;
|
|
8
25
|
};
|
|
9
|
-
/**
|
|
10
|
-
export declare function toFieldItems<Value>(options: readonly FieldOption<Value>[]):
|
|
26
|
+
/** Options as picker items, groups kept where they sit. */
|
|
27
|
+
export declare function toFieldItems<Value>(options: readonly FieldOption<Value>[]): ComboboxItems<Value>;
|
|
28
|
+
/** Every item, groups flattened. */
|
|
29
|
+
export declare const leaves: (items: ComboboxItems<unknown>) => (ChoiceItem | string)[];
|
|
30
|
+
export declare const itemValue: (item: ChoiceItem | string) => unknown;
|
|
31
|
+
/** The item among `items` holding `value`, with what it sets through `secondaryField`. */
|
|
32
|
+
export declare function findChoice(items: ComboboxItems<unknown>, value: unknown): ChoiceItem | undefined;
|
|
@@ -1,11 +1,32 @@
|
|
|
1
|
+
import { comboboxLeaves } from "../../components/comboboxItems.js";
|
|
2
|
+
import { dequal } from "dequal";
|
|
1
3
|
//#region src/form/utils/fieldItems.ts
|
|
2
|
-
|
|
4
|
+
var isGroup = (option) => typeof option === "object" && "options" in option;
|
|
5
|
+
function toItem(option) {
|
|
6
|
+
if (typeof option === "string") return option;
|
|
7
|
+
const { label, value, description, secondaryLabel, secondaryValue, disabled, icon } = option;
|
|
8
|
+
return {
|
|
9
|
+
label,
|
|
10
|
+
value,
|
|
11
|
+
description: description ?? secondaryLabel,
|
|
12
|
+
disabled,
|
|
13
|
+
icon,
|
|
14
|
+
...secondaryValue === void 0 ? {} : { secondaryValue }
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** Options as picker items, groups kept where they sit. */
|
|
3
18
|
function toFieldItems(options) {
|
|
4
|
-
return options.map((option) =>
|
|
5
|
-
label: option.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
19
|
+
return options.map((option) => isGroup(option) ? {
|
|
20
|
+
label: option.category ?? "",
|
|
21
|
+
items: option.options.map(toItem)
|
|
22
|
+
} : toItem(option));
|
|
23
|
+
}
|
|
24
|
+
/** Every item, groups flattened. */
|
|
25
|
+
var leaves = (items) => comboboxLeaves(items);
|
|
26
|
+
var itemValue = (item) => typeof item === "string" ? item : item.value;
|
|
27
|
+
/** The item among `items` holding `value`, with what it sets through `secondaryField`. */
|
|
28
|
+
function findChoice(items, value) {
|
|
29
|
+
return leaves(items).find((item) => typeof item === "object" && dequal(item.value, value));
|
|
9
30
|
}
|
|
10
31
|
//#endregion
|
|
11
|
-
export { toFieldItems };
|
|
32
|
+
export { findChoice, itemValue, leaves, toFieldItems };
|
package/dist/list/rowMenu.d.ts
CHANGED
|
@@ -53,11 +53,16 @@ export type CopySubmenu = (fields: CopyFields) => MenuSubmenu;
|
|
|
53
53
|
/** Returns a builder for the Copy submenu, labeled from the provider's strings. */
|
|
54
54
|
export declare function useCopySubmenu(): CopySubmenu;
|
|
55
55
|
/** Opens a row's menu under itself. Shows on the row's hover or focus, and
|
|
56
|
-
* stays while its menu is `open`, though the pointer has left the row.
|
|
57
|
-
|
|
56
|
+
* stays while its menu is `open`, though the pointer has left the row. A touch
|
|
57
|
+
* screen has no hover, so there it always shows: the row's menu is otherwise
|
|
58
|
+
* out of reach. */
|
|
59
|
+
export declare function MoreButton({ onOpen, open, visible, label, className, }: {
|
|
58
60
|
onOpen: (button: HTMLElement) => void;
|
|
59
61
|
open?: boolean;
|
|
60
62
|
/** Shows at rest too. */
|
|
61
63
|
visible?: boolean;
|
|
64
|
+
/** Names the row the menu acts on, as "More actions for Core", for a list
|
|
65
|
+
* of rows that would otherwise each read "More actions". */
|
|
66
|
+
label?: string | undefined;
|
|
62
67
|
className?: string | undefined;
|
|
63
68
|
}): import("react").JSX.Element;
|
package/dist/list/rowMenu.js
CHANGED
|
@@ -114,19 +114,21 @@ function useCopySubmenu() {
|
|
|
114
114
|
}, [t]);
|
|
115
115
|
}
|
|
116
116
|
/** Opens a row's menu under itself. Shows on the row's hover or focus, and
|
|
117
|
-
* stays while its menu is `open`, though the pointer has left the row.
|
|
118
|
-
|
|
117
|
+
* stays while its menu is `open`, though the pointer has left the row. A touch
|
|
118
|
+
* screen has no hover, so there it always shows: the row's menu is otherwise
|
|
119
|
+
* out of reach. */
|
|
120
|
+
function MoreButton({ onOpen, open = false, visible = open, label, className }) {
|
|
119
121
|
const { list: t } = useStrings();
|
|
120
122
|
return /* @__PURE__ */ jsx("button", {
|
|
121
123
|
type: "button",
|
|
122
|
-
"aria-label": t.moreActions,
|
|
124
|
+
"aria-label": label ?? t.moreActions,
|
|
123
125
|
"aria-haspopup": "menu",
|
|
124
126
|
"aria-expanded": open,
|
|
125
127
|
onClick: (e) => {
|
|
126
128
|
e.stopPropagation();
|
|
127
129
|
onOpen(e.currentTarget);
|
|
128
130
|
},
|
|
129
|
-
className: clsx("rounded p-1 cursor-pointer text-(--theme-muted-text-color) transition-colors hover:bg-(--theme-muted-panel-bg) hover:text-(--theme-app) focus-visible:opacity-100", visible ? "opacity-100" : "opacity-0 group-hover:opacity-100", className),
|
|
131
|
+
className: clsx("rounded p-1 cursor-pointer text-(--theme-muted-text-color) transition-colors hover:bg-(--theme-muted-panel-bg) hover:text-(--theme-app) focus-visible:opacity-100", visible ? "opacity-100" : "opacity-0 group-hover:opacity-100 pointer-coarse:opacity-100", className),
|
|
130
132
|
children: /* @__PURE__ */ jsx(BsThreeDots, { className: "h-4 w-4" })
|
|
131
133
|
});
|
|
132
134
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parallelworks/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.44.0",
|
|
4
4
|
"description": "React UI components: a theme contract, icons, primitives, lists, forms, a job graph, a code editor, a log viewer, a file explorer and an AI chat, each on its own subpath",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|