@intentface/ui 0.0.0-stage → 0.7.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/LICENSE +21 -0
- package/README.md +147 -2
- package/dist/ask-user/ask-user.d.ts +104 -0
- package/dist/ask-user/ask-user.js +245 -0
- package/dist/ask-user/index.d.ts +3 -0
- package/dist/ask-user/index.js +3 -0
- package/dist/ask-user/index.parts.d.ts +1 -0
- package/dist/ask-user/index.parts.js +5 -0
- package/dist/attachments/attachments.d.ts +73 -0
- package/dist/attachments/attachments.js +121 -0
- package/dist/attachments/index.d.ts +3 -0
- package/dist/attachments/index.js +3 -0
- package/dist/attachments/index.parts.d.ts +1 -0
- package/dist/attachments/index.parts.js +5 -0
- package/dist/chip/chip.d.ts +45 -0
- package/dist/chip/chip.js +43 -0
- package/dist/chip/index.d.ts +2 -0
- package/dist/chip/index.js +2 -0
- package/dist/chip/index.parts.d.ts +1 -0
- package/dist/chip/index.parts.js +5 -0
- package/dist/chip-markdown.d.ts +25 -0
- package/dist/chip-markdown.js +54 -0
- package/dist/composer/actions.d.ts +35 -0
- package/dist/composer/actions.js +76 -0
- package/dist/composer/ask-user-machine.d.ts +52 -0
- package/dist/composer/ask-user-machine.js +164 -0
- package/dist/composer/attachments-machine.d.ts +34 -0
- package/dist/composer/attachments-machine.js +91 -0
- package/dist/composer/attachments.d.ts +16 -0
- package/dist/composer/attachments.js +32 -0
- package/dist/composer/command-list.d.ts +49 -0
- package/dist/composer/command-list.js +360 -0
- package/dist/composer/commands.d.ts +28 -0
- package/dist/composer/commands.js +56 -0
- package/dist/composer/container.d.ts +3 -0
- package/dist/composer/container.js +32 -0
- package/dist/composer/controller.d.ts +3 -0
- package/dist/composer/controller.js +6 -0
- package/dist/composer/editor-dom.d.ts +57 -0
- package/dist/composer/editor-dom.js +517 -0
- package/dist/composer/fuzzy.d.ts +4 -0
- package/dist/composer/fuzzy.js +49 -0
- package/dist/composer/index.d.ts +25 -0
- package/dist/composer/index.js +13 -0
- package/dist/composer/index.parts.d.ts +11 -0
- package/dist/composer/index.parts.js +15 -0
- package/dist/composer/internals.d.ts +30 -0
- package/dist/composer/internals.js +102 -0
- package/dist/composer/keyboard.d.ts +61 -0
- package/dist/composer/keyboard.js +75 -0
- package/dist/composer/panel.d.ts +28 -0
- package/dist/composer/panel.js +76 -0
- package/dist/composer/placeholder.d.ts +10 -0
- package/dist/composer/placeholder.js +8 -0
- package/dist/composer/popover.d.ts +12 -0
- package/dist/composer/popover.js +62 -0
- package/dist/composer/prefix-detection.d.ts +23 -0
- package/dist/composer/prefix-detection.js +67 -0
- package/dist/composer/root.d.ts +21 -0
- package/dist/composer/root.js +124 -0
- package/dist/composer/segments.d.ts +108 -0
- package/dist/composer/segments.js +366 -0
- package/dist/composer/store.d.ts +98 -0
- package/dist/composer/store.js +457 -0
- package/dist/composer/textarea.d.ts +39 -0
- package/dist/composer/textarea.js +64 -0
- package/dist/composer/trigger-tracker.d.ts +12 -0
- package/dist/composer/trigger-tracker.js +90 -0
- package/dist/composer/types.d.ts +127 -0
- package/dist/composer/types.js +4 -0
- package/dist/composer/use-composer-editor.d.ts +43 -0
- package/dist/composer/use-composer-editor.js +653 -0
- package/dist/generative/catalog.d.ts +177 -0
- package/dist/generative/catalog.js +20 -0
- package/dist/generative/index.d.ts +26 -0
- package/dist/generative/index.js +20 -0
- package/dist/generative/json-schema.d.ts +45 -0
- package/dist/generative/json-schema.js +216 -0
- package/dist/generative/markdown.d.ts +21 -0
- package/dist/generative/markdown.js +76 -0
- package/dist/generative/tree.d.ts +56 -0
- package/dist/generative/tree.js +365 -0
- package/dist/generative/use-tree.d.ts +12 -0
- package/dist/generative/use-tree.js +64 -0
- package/dist/internal/change-event-details.d.ts +12 -0
- package/dist/internal/change-event-details.js +16 -0
- package/dist/internal/collapsible.d.ts +22 -0
- package/dist/internal/collapsible.js +94 -0
- package/dist/internal/collection.d.ts +32 -0
- package/dist/internal/collection.js +72 -0
- package/dist/internal/iso-layout-effect.d.ts +2 -0
- package/dist/internal/iso-layout-effect.js +6 -0
- package/dist/internal/portal.d.ts +6 -0
- package/dist/internal/portal.js +18 -0
- package/dist/internal/primitive-props.d.ts +4 -0
- package/dist/internal/primitive-props.js +3 -0
- package/dist/internal/render/baseTypes.d.ts +18 -0
- package/dist/internal/render/baseTypes.js +4 -0
- package/dist/internal/render/empty.d.ts +3 -0
- package/dist/internal/render/empty.js +5 -0
- package/dist/internal/render/getReactElementRef.d.ts +5 -0
- package/dist/internal/render/getReactElementRef.js +15 -0
- package/dist/internal/render/getStateAttributesProps.d.ts +4 -0
- package/dist/internal/render/getStateAttributesProps.js +23 -0
- package/dist/internal/render/mergeObjects.d.ts +1 -0
- package/dist/internal/render/mergeObjects.js +14 -0
- package/dist/internal/render/mergeProps.d.ts +55 -0
- package/dist/internal/render/mergeProps.js +183 -0
- package/dist/internal/render/positioning.d.ts +33 -0
- package/dist/internal/render/positioning.js +112 -0
- package/dist/internal/render/reactVersion.d.ts +3 -0
- package/dist/internal/render/reactVersion.js +7 -0
- package/dist/internal/render/resolveClassName.d.ts +8 -0
- package/dist/internal/render/resolveClassName.js +12 -0
- package/dist/internal/render/resolveStyle.d.ts +8 -0
- package/dist/internal/render/resolveStyle.js +12 -0
- package/dist/internal/render/transition.d.ts +47 -0
- package/dist/internal/render/transition.js +259 -0
- package/dist/internal/render/types.d.ts +74 -0
- package/dist/internal/render/types.js +3 -0
- package/dist/internal/render/useMergedRefs.d.ts +21 -0
- package/dist/internal/render/useMergedRefs.js +102 -0
- package/dist/internal/render/useRefWithInit.d.ts +10 -0
- package/dist/internal/render/useRefWithInit.js +12 -0
- package/dist/internal/render/useRender.d.ts +71 -0
- package/dist/internal/render/useRender.js +12 -0
- package/dist/internal/render/useRenderElement.d.ts +62 -0
- package/dist/internal/render/useRenderElement.js +157 -0
- package/dist/internal/render/warn.d.ts +1 -0
- package/dist/internal/render/warn.js +15 -0
- package/dist/internal/safe-polygon.d.ts +21 -0
- package/dist/internal/safe-polygon.js +179 -0
- package/dist/internal/scroll-into-view-within.d.ts +1 -0
- package/dist/internal/scroll-into-view-within.js +26 -0
- package/dist/internal/standard-schema.d.ts +38 -0
- package/dist/internal/standard-schema.js +4 -0
- package/dist/internal/state-mappings.d.ts +8 -0
- package/dist/internal/state-mappings.js +17 -0
- package/dist/internal/use-controlled.d.ts +19 -0
- package/dist/internal/use-controlled.js +33 -0
- package/dist/message/index.d.ts +3 -0
- package/dist/message/index.js +3 -0
- package/dist/message/index.parts.d.ts +1 -0
- package/dist/message/index.parts.js +5 -0
- package/dist/message/message.d.ts +58 -0
- package/dist/message/message.js +93 -0
- package/dist/message-utils.d.ts +55 -0
- package/dist/message-utils.js +79 -0
- package/dist/nav/index.d.ts +4 -0
- package/dist/nav/index.js +5 -0
- package/dist/nav/index.parts.d.ts +3 -0
- package/dist/nav/index.parts.js +7 -0
- package/dist/nav/nav.d.ts +152 -0
- package/dist/nav/nav.js +660 -0
- package/dist/nav/store.d.ts +50 -0
- package/dist/nav/store.js +95 -0
- package/dist/reasoning/index.d.ts +3 -0
- package/dist/reasoning/index.js +3 -0
- package/dist/reasoning/index.parts.d.ts +1 -0
- package/dist/reasoning/index.parts.js +5 -0
- package/dist/reasoning/reasoning.d.ts +38 -0
- package/dist/reasoning/reasoning.js +67 -0
- package/dist/shell/index.d.ts +5 -0
- package/dist/shell/index.js +5 -0
- package/dist/shell/index.parts.d.ts +3 -0
- package/dist/shell/index.parts.js +7 -0
- package/dist/shell/shell.d.ts +108 -0
- package/dist/shell/shell.js +390 -0
- package/dist/shell/store.d.ts +112 -0
- package/dist/shell/store.js +134 -0
- package/dist/steps/index.d.ts +2 -0
- package/dist/steps/index.js +2 -0
- package/dist/steps/index.parts.d.ts +1 -0
- package/dist/steps/index.parts.js +5 -0
- package/dist/steps/steps.d.ts +90 -0
- package/dist/steps/steps.js +116 -0
- package/dist/tabs/anchored.d.ts +59 -0
- package/dist/tabs/anchored.js +109 -0
- package/dist/tabs/hover.d.ts +33 -0
- package/dist/tabs/hover.js +169 -0
- package/dist/tabs/index.d.ts +5 -0
- package/dist/tabs/index.js +4 -0
- package/dist/tabs/index.parts.d.ts +4 -0
- package/dist/tabs/index.parts.js +8 -0
- package/dist/tabs/store.d.ts +131 -0
- package/dist/tabs/store.js +248 -0
- package/dist/tabs/tabs.d.ts +217 -0
- package/dist/tabs/tabs.js +458 -0
- package/dist/thread/geometry.d.ts +23 -0
- package/dist/thread/geometry.js +48 -0
- package/dist/thread/index.d.ts +4 -0
- package/dist/thread/index.js +3 -0
- package/dist/thread/index.parts.d.ts +1 -0
- package/dist/thread/index.parts.js +5 -0
- package/dist/thread/stores.d.ts +21 -0
- package/dist/thread/stores.js +63 -0
- package/dist/thread/thread.d.ts +57 -0
- package/dist/thread/thread.js +663 -0
- package/dist/types.d.ts +58 -0
- package/dist/types.js +17 -0
- package/package.json +125 -4
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type AttachmentErrorCode, type AttachmentItem } from "../attachments/index.js";
|
|
2
|
+
export type AttachmentStoreState = {
|
|
3
|
+
items: AttachmentItem[];
|
|
4
|
+
error: AttachmentErrorCode | null;
|
|
5
|
+
};
|
|
6
|
+
export type AttachmentStoreAction = {
|
|
7
|
+
type: "add";
|
|
8
|
+
files: File[] | FileList;
|
|
9
|
+
} | {
|
|
10
|
+
type: "remove";
|
|
11
|
+
id: string;
|
|
12
|
+
} | {
|
|
13
|
+
type: "reset";
|
|
14
|
+
};
|
|
15
|
+
export type AttachmentStoreConfig = {
|
|
16
|
+
accept: string;
|
|
17
|
+
maxFiles: number;
|
|
18
|
+
maxFileSize: number;
|
|
19
|
+
convert: (file: File) => AttachmentItem;
|
|
20
|
+
destroy: (item: AttachmentItem) => void;
|
|
21
|
+
};
|
|
22
|
+
export declare const INITIAL_ATTACHMENT_STATE: AttachmentStoreState;
|
|
23
|
+
export declare const attachmentReducer: (state: AttachmentStoreState, action: AttachmentStoreAction, config: AttachmentStoreConfig) => AttachmentStoreState;
|
|
24
|
+
export type DragHandlerCallbacks = {
|
|
25
|
+
isInScope: (event: DragEvent) => boolean;
|
|
26
|
+
onFiles: (files: FileList) => void;
|
|
27
|
+
setDragging: (active: boolean) => void;
|
|
28
|
+
};
|
|
29
|
+
export declare const createDragHandlers: (callbacks: DragHandlerCallbacks) => {
|
|
30
|
+
onDragOver: (event: Event) => void;
|
|
31
|
+
onDragEnter: (event: Event) => void;
|
|
32
|
+
onDragLeave: (event: Event) => void;
|
|
33
|
+
onDrop: (event: Event) => void;
|
|
34
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Attachments — pure store reducer that validates and accumulates files, plus
|
|
2
|
+
// the drag-and-drop handler factory. The store owns the state cell; this owns
|
|
3
|
+
// the rules.
|
|
4
|
+
import { matchesAccept } from "../attachments/index.js";
|
|
5
|
+
export const INITIAL_ATTACHMENT_STATE = {
|
|
6
|
+
items: [],
|
|
7
|
+
error: null,
|
|
8
|
+
};
|
|
9
|
+
export const attachmentReducer = (state, action, config) => {
|
|
10
|
+
switch (action.type) {
|
|
11
|
+
case "add": {
|
|
12
|
+
const incoming = [...action.files];
|
|
13
|
+
if (!incoming.length)
|
|
14
|
+
return state;
|
|
15
|
+
const accepted = incoming.filter((file) => matchesAccept(file, config.accept));
|
|
16
|
+
if (!accepted.length) {
|
|
17
|
+
return { ...state, error: "accept" };
|
|
18
|
+
}
|
|
19
|
+
const sized = accepted.filter((file) => file.size <= config.maxFileSize);
|
|
20
|
+
if (!sized.length) {
|
|
21
|
+
return { ...state, error: "max_file_size" };
|
|
22
|
+
}
|
|
23
|
+
const capacity = Math.max(0, config.maxFiles - state.items.length);
|
|
24
|
+
const capped = sized.slice(0, capacity);
|
|
25
|
+
const overCapacityError = sized.length > capacity ? "max_files" : null;
|
|
26
|
+
if (!capped.length) {
|
|
27
|
+
return { ...state, error: overCapacityError ?? state.error };
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
items: [...state.items, ...capped.map(config.convert)],
|
|
31
|
+
error: overCapacityError,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
case "remove": {
|
|
35
|
+
const found = state.items.find((item) => item.id === action.id);
|
|
36
|
+
if (found)
|
|
37
|
+
config.destroy(found);
|
|
38
|
+
return {
|
|
39
|
+
items: state.items.filter((item) => item.id !== action.id),
|
|
40
|
+
error: null,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
case "reset": {
|
|
44
|
+
for (const item of state.items)
|
|
45
|
+
config.destroy(item);
|
|
46
|
+
return INITIAL_ATTACHMENT_STATE;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
export const createDragHandlers = (callbacks) => {
|
|
51
|
+
const counter = { current: 0 };
|
|
52
|
+
const carriesFiles = (event) => event.dataTransfer?.types?.includes("Files") ?? false;
|
|
53
|
+
return {
|
|
54
|
+
onDragOver: (event) => {
|
|
55
|
+
const dragEvent = event;
|
|
56
|
+
if (!callbacks.isInScope(dragEvent))
|
|
57
|
+
return;
|
|
58
|
+
if (carriesFiles(dragEvent))
|
|
59
|
+
dragEvent.preventDefault();
|
|
60
|
+
},
|
|
61
|
+
onDragEnter: (event) => {
|
|
62
|
+
const dragEvent = event;
|
|
63
|
+
if (!callbacks.isInScope(dragEvent))
|
|
64
|
+
return;
|
|
65
|
+
if (carriesFiles(dragEvent)) {
|
|
66
|
+
counter.current++;
|
|
67
|
+
callbacks.setDragging(true);
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
onDragLeave: (event) => {
|
|
71
|
+
const dragEvent = event;
|
|
72
|
+
if (!callbacks.isInScope(dragEvent))
|
|
73
|
+
return;
|
|
74
|
+
counter.current--;
|
|
75
|
+
if (counter.current === 0)
|
|
76
|
+
callbacks.setDragging(false);
|
|
77
|
+
},
|
|
78
|
+
onDrop: (event) => {
|
|
79
|
+
const dragEvent = event;
|
|
80
|
+
if (!callbacks.isInScope(dragEvent))
|
|
81
|
+
return;
|
|
82
|
+
if (carriesFiles(dragEvent))
|
|
83
|
+
dragEvent.preventDefault();
|
|
84
|
+
counter.current = 0;
|
|
85
|
+
callbacks.setDragging(false);
|
|
86
|
+
const files = dragEvent.dataTransfer?.files;
|
|
87
|
+
if (files && files.length > 0)
|
|
88
|
+
callbacks.onFiles(files);
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { type AttachmentItem } from "../attachments/index.js";
|
|
3
|
+
import type { PrimitiveProps } from "../internal/primitive-props.js";
|
|
4
|
+
export type ComposerAttachmentsProps = {
|
|
5
|
+
convert?: (file: File) => AttachmentItem;
|
|
6
|
+
destroy?: (item: AttachmentItem) => void;
|
|
7
|
+
accept?: string;
|
|
8
|
+
maxFiles?: number;
|
|
9
|
+
maxFileSize?: number;
|
|
10
|
+
multiple?: boolean;
|
|
11
|
+
globalDrop?: boolean;
|
|
12
|
+
children?: ReactNode;
|
|
13
|
+
};
|
|
14
|
+
export declare const ComposerAttachments: ({ convert, destroy, accept, maxFiles, maxFileSize, multiple, globalDrop, children, }: ComposerAttachmentsProps) => import("react").JSX.Element;
|
|
15
|
+
export type ComposerAttachmentTriggerProps = PrimitiveProps<"button">;
|
|
16
|
+
export declare const ComposerAttachmentTrigger: ({ className, render, style, ...elementProps }: ComposerAttachmentTriggerProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { revokeAttachmentUrl, toAttachmentItem } from "../attachments/index.js";
|
|
4
|
+
import { useRenderElement } from "../internal/render/useRenderElement.js";
|
|
5
|
+
import { useComposer, useComposerContextStore } from "./store.js";
|
|
6
|
+
export const ComposerAttachments = ({ convert = toAttachmentItem, destroy = revokeAttachmentUrl, accept = "", maxFiles = Number.POSITIVE_INFINITY, maxFileSize = Number.POSITIVE_INFINITY, multiple = true, globalDrop = false, children, }) => {
|
|
7
|
+
const store = useComposerContextStore();
|
|
8
|
+
const attachments = useComposer((composer) => composer.attachments);
|
|
9
|
+
store.attachmentConfigRef.current = { accept, maxFiles, maxFileSize, convert, destroy };
|
|
10
|
+
attachments.globalDropRef.current = globalDrop;
|
|
11
|
+
const handleFileChange = (event) => {
|
|
12
|
+
if (event.currentTarget.files) {
|
|
13
|
+
attachments.add(event.currentTarget.files);
|
|
14
|
+
}
|
|
15
|
+
event.currentTarget.value = "";
|
|
16
|
+
};
|
|
17
|
+
return (_jsxs(_Fragment, { children: [_jsx("input", { accept: accept, style: { display: "none" }, multiple: multiple, onChange: handleFileChange, ref: attachments.fileInputRef, type: "file" }), children] }));
|
|
18
|
+
};
|
|
19
|
+
export const ComposerAttachmentTrigger = ({ className, render, style, ...elementProps }) => {
|
|
20
|
+
const attachments = useComposer((composer) => composer.attachments);
|
|
21
|
+
return useRenderElement("button", { className, render, style }, {
|
|
22
|
+
props: [
|
|
23
|
+
{
|
|
24
|
+
// Default overridable name — the trigger is typically icon-only.
|
|
25
|
+
"aria-label": "Add attachment",
|
|
26
|
+
"data-composer-attachment-trigger": "",
|
|
27
|
+
onClick: () => attachments.openFileDialog(),
|
|
28
|
+
},
|
|
29
|
+
elementProps,
|
|
30
|
+
],
|
|
31
|
+
});
|
|
32
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type ComponentProps, type ReactNode } from "react";
|
|
2
|
+
import type { PrimitiveProps } from "../internal/primitive-props.js";
|
|
3
|
+
import { Commands } from "./commands.js";
|
|
4
|
+
import type { CommandItemData } from "./types.js";
|
|
5
|
+
export type CommandListState = "loading" | "empty" | "ready";
|
|
6
|
+
export declare const optionDomId: (listboxId: string, value: string) => string;
|
|
7
|
+
export type ComposerCommandState = {
|
|
8
|
+
/** Present as data-loading while an async items callback is in flight. */
|
|
9
|
+
loading: boolean;
|
|
10
|
+
/** Present as data-empty when nothing matches the query. */
|
|
11
|
+
empty: boolean;
|
|
12
|
+
};
|
|
13
|
+
export type ComposerCommandProps = PrimitiveProps<"div", ComposerCommandState> & {
|
|
14
|
+
prefix: string;
|
|
15
|
+
};
|
|
16
|
+
export declare const ComposerCommand: ({ prefix, className, render, style, children, ...elementProps }: ComposerCommandProps) => import("react").JSX.Element | null;
|
|
17
|
+
export declare const useCommandListItems: <Item extends CommandItemData = CommandItemData>() => {
|
|
18
|
+
items: Item[];
|
|
19
|
+
state: CommandListState;
|
|
20
|
+
};
|
|
21
|
+
export type ComposerCommandListProps<Item extends CommandItemData> = Omit<PrimitiveProps<"div">, "children"> & {
|
|
22
|
+
children: (item: Item) => ReactNode;
|
|
23
|
+
};
|
|
24
|
+
export declare const ComposerCommandList: <Item extends CommandItemData>({ className, render, style, children: renderItem, ...elementProps }: ComposerCommandListProps<Item>) => ReactNode;
|
|
25
|
+
export type ComposerCommandLoadingProps = PrimitiveProps<"div">;
|
|
26
|
+
export declare const ComposerCommandLoading: ({ children, className, render, style, ...elementProps }: ComposerCommandLoadingProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
27
|
+
export type ComposerCommandEmptyProps = PrimitiveProps<"div">;
|
|
28
|
+
export declare const ComposerCommandEmpty: ({ children, className, render, style, ...elementProps }: ComposerCommandEmptyProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
29
|
+
export type ComposerCommandDismissProps = PrimitiveProps<"button">;
|
|
30
|
+
export declare const ComposerCommandDismiss: ({ children, className, render, style, ...elementProps }: ComposerCommandDismissProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
31
|
+
export type ComposerCommandItemProps = Omit<ComponentProps<typeof Commands.Item>, "highlighted" | "disabled"> & {
|
|
32
|
+
value: string;
|
|
33
|
+
};
|
|
34
|
+
export declare const ComposerCommandItem: ({ value, ...props }: ComposerCommandItemProps) => import("react").JSX.Element;
|
|
35
|
+
export type ComposerCommandItemIconProps = PrimitiveProps<"span">;
|
|
36
|
+
export declare const ComposerCommandItemIcon: ({ className, render, style, ...elementProps }: ComposerCommandItemIconProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
37
|
+
export type ComposerCommandItemLabelProps = PrimitiveProps<"span">;
|
|
38
|
+
export declare const ComposerCommandItemLabel: ({ className, render, style, ...elementProps }: ComposerCommandItemLabelProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
39
|
+
export type ComposerCommandItemDescriptionProps = PrimitiveProps<"span">;
|
|
40
|
+
export declare const ComposerCommandItemDescription: ({ className, render, style, ...elementProps }: ComposerCommandItemDescriptionProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
41
|
+
export type ComposerCommandGroupProps<Item extends CommandItemData = CommandItemData> = {
|
|
42
|
+
/** Bucket the resolved items by this key (defines the app's grouping taxonomy). */
|
|
43
|
+
groupBy: (item: Item) => string;
|
|
44
|
+
/** Render one group: its key + that group's already-filtered items. */
|
|
45
|
+
children: (group: string, items: Item[]) => ReactNode;
|
|
46
|
+
};
|
|
47
|
+
export declare const ComposerCommandGroup: <Item extends CommandItemData = CommandItemData>({ groupBy, children: renderGroup, }: ComposerCommandGroupProps<Item>) => ReactNode;
|
|
48
|
+
export type ComposerCommandGroupLabelProps = PrimitiveProps<"div">;
|
|
49
|
+
export declare const ComposerCommandGroupLabel: ({ className, render, style, ...elementProps }: ComposerCommandGroupLabelProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
// Composer.Command family — resolution (sync/async items, fuzzy filter),
|
|
4
|
+
// keyboard/mouse selection, chip insertion, dismissal, and the nav/items
|
|
5
|
+
// context split (highlight changes don't re-render the items map, and items
|
|
6
|
+
// mutations don't re-render every row). All unstyled; state panels as
|
|
7
|
+
// data-state / data-highlighted.
|
|
8
|
+
import { createContext, Fragment, use, useCallback, useEffect, useMemo, useRef, useState, } from "react";
|
|
9
|
+
import { useRenderElement } from "../internal/render/useRenderElement.js";
|
|
10
|
+
import { Commands } from "./commands.js";
|
|
11
|
+
import { filterArrayItems, suggestionRemainder } from "./fuzzy.js";
|
|
12
|
+
import { useAsRef, useComposerInternals, useIsomorphicLayoutEffect } from "./internals.js";
|
|
13
|
+
import { useComposer, useComposerContextStore } from "./store.js";
|
|
14
|
+
const CommandListNavContext = createContext(null);
|
|
15
|
+
const CommandListItemsContext = createContext(null);
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Item resolution — turn the prefix's items config (array or async callback)
|
|
18
|
+
// into the current filtered list + loading/empty/ready state.
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
const useResolvedItems = (itemsProp, query, isActive) => {
|
|
21
|
+
const isCallback = typeof itemsProp === "function";
|
|
22
|
+
const [asyncState, setAsyncState] = useState(() => ({ items: [], loading: isCallback }));
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (typeof itemsProp !== "function")
|
|
25
|
+
return;
|
|
26
|
+
if (!isActive)
|
|
27
|
+
return;
|
|
28
|
+
const controller = new AbortController();
|
|
29
|
+
let cancelled = false;
|
|
30
|
+
let result;
|
|
31
|
+
try {
|
|
32
|
+
result = itemsProp(query, { signal: controller.signal });
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
console.warn("Composer.commands items callback threw:", error);
|
|
36
|
+
setAsyncState({ items: [], loading: false });
|
|
37
|
+
return () => {
|
|
38
|
+
cancelled = true;
|
|
39
|
+
controller.abort();
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (result instanceof Promise) {
|
|
43
|
+
setAsyncState((previous) => ({ ...previous, loading: true }));
|
|
44
|
+
result.then((resolved) => {
|
|
45
|
+
if (cancelled)
|
|
46
|
+
return;
|
|
47
|
+
setAsyncState({ items: resolved, loading: false });
|
|
48
|
+
}, (error) => {
|
|
49
|
+
if (cancelled)
|
|
50
|
+
return;
|
|
51
|
+
if (error?.name === "AbortError")
|
|
52
|
+
return;
|
|
53
|
+
console.warn("Composer.commands items callback rejected:", error);
|
|
54
|
+
setAsyncState((previous) => ({ ...previous, loading: false }));
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
setAsyncState({ items: result, loading: false });
|
|
59
|
+
}
|
|
60
|
+
return () => {
|
|
61
|
+
cancelled = true;
|
|
62
|
+
controller.abort();
|
|
63
|
+
};
|
|
64
|
+
}, [itemsProp, query, isActive]);
|
|
65
|
+
if (Array.isArray(itemsProp)) {
|
|
66
|
+
const items = filterArrayItems(itemsProp, query);
|
|
67
|
+
return { items, state: items.length === 0 ? "empty" : "ready" };
|
|
68
|
+
}
|
|
69
|
+
if (asyncState.loading) {
|
|
70
|
+
// Locally filter the most-recent resolved set so typing feels instant
|
|
71
|
+
// while the new fetch is in flight. The server result replaces this once
|
|
72
|
+
// it lands.
|
|
73
|
+
return {
|
|
74
|
+
items: filterArrayItems(asyncState.items, query),
|
|
75
|
+
state: "loading",
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
items: asyncState.items,
|
|
80
|
+
state: asyncState.items.length === 0 ? "empty" : "ready",
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Composer.Command — the orchestrator: resolves items for the active
|
|
85
|
+
// prefix, owns the highlight/selection/dismiss logic, and provides the two
|
|
86
|
+
// contexts. Renders null unless this prefix is the active one.
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
const EMPTY_ITEMS = [];
|
|
89
|
+
// Deterministic option-row id shared by the row itself and the editor's
|
|
90
|
+
// aria-activedescendant. Values sanitize to valid id tokens.
|
|
91
|
+
export const optionDomId = (listboxId, value) => `${listboxId}-option-${value.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
|
|
92
|
+
// The badge's hint chrome: non-editable so the caret can't enter it, no chip
|
|
93
|
+
// id so the reader/mappers treat it as zero-width presentation, aria-hidden
|
|
94
|
+
// so screen readers skip the ghost. Styled via its part attribute like any other part.
|
|
95
|
+
const createHintElement = (badge) => {
|
|
96
|
+
const hint = badge.ownerDocument.createElement("span");
|
|
97
|
+
hint.setAttribute("data-command-hint", "");
|
|
98
|
+
hint.setAttribute("contenteditable", "false");
|
|
99
|
+
hint.setAttribute("aria-hidden", "true");
|
|
100
|
+
badge.appendChild(hint);
|
|
101
|
+
return hint;
|
|
102
|
+
};
|
|
103
|
+
export const ComposerCommand = ({ prefix, className, render, style, children, ...elementProps }) => {
|
|
104
|
+
const store = useComposerContextStore();
|
|
105
|
+
const internals = useComposerInternals();
|
|
106
|
+
// Renders while this prefix is the active one AND through its close animation. `active`
|
|
107
|
+
// flips false the instant the trigger closes, but the store keeps `present` sticky until
|
|
108
|
+
// the Panel's exit animation finishes (finalizePanelClose). `wasActive` marks that this
|
|
109
|
+
// prefix — not a sibling — is the one closing, so only it keeps rendering during the window.
|
|
110
|
+
const isActive = useComposer((composer) => composer.commands.active && composer.commands.trigger === prefix);
|
|
111
|
+
const present = useComposer((composer) => composer.commands.present);
|
|
112
|
+
const query = useComposer((composer) => composer.commands.query);
|
|
113
|
+
// State adjusted during render (not a ref React might keep from a discarded
|
|
114
|
+
// render); it only flips at open and at the end of the close, so it costs a
|
|
115
|
+
// re-render at those two moments and nowhere else.
|
|
116
|
+
const [wasActive, setWasActive] = useState(false);
|
|
117
|
+
if (isActive && !wasActive)
|
|
118
|
+
setWasActive(true);
|
|
119
|
+
else if (!isActive && !present && wasActive)
|
|
120
|
+
setWasActive(false);
|
|
121
|
+
const isPresent = isActive || (present && wasActive);
|
|
122
|
+
const config = internals.commands[prefix];
|
|
123
|
+
const itemsProp = config?.items ?? EMPTY_ITEMS;
|
|
124
|
+
const kind = config?.kind ?? "execute";
|
|
125
|
+
// Freeze the last resolved items while closing so the exiting panel shows what it had,
|
|
126
|
+
// not a re-filter/re-fetch against the now-empty query.
|
|
127
|
+
const resolved = useResolvedItems(itemsProp, query, isActive);
|
|
128
|
+
const lastResolvedRef = useRef(resolved);
|
|
129
|
+
if (isActive)
|
|
130
|
+
lastResolvedRef.current = resolved;
|
|
131
|
+
const { items, state } = isActive ? resolved : lastResolvedRef.current;
|
|
132
|
+
// The highlight index lives in the store (so the editor's keydown handler can
|
|
133
|
+
// move it). It's raw/unbounded; wrap it by the current item count here. The
|
|
134
|
+
// wrap runs over the enabled subset only, so disabled rows render but never
|
|
135
|
+
// take the highlight — arrow keys hop over them for free. An empty list has
|
|
136
|
+
// no highlighted row — the "No results" row stands in as the sole selectable.
|
|
137
|
+
const enabledItems = useMemo(() => items.filter((item) => !item.disabled), [items]);
|
|
138
|
+
const highlightIndex = useComposer((composer) => composer.commands.highlightIndex);
|
|
139
|
+
const activeIndex = enabledItems.length > 0
|
|
140
|
+
? ((highlightIndex % enabledItems.length) + enabledItems.length) % enabledItems.length
|
|
141
|
+
: -1;
|
|
142
|
+
const highlightedItem = enabledItems[activeIndex] ?? null;
|
|
143
|
+
const effectiveHighlight = highlightedItem?.value ?? null;
|
|
144
|
+
// Badge hint — one real element (span[data-command-hint], appended
|
|
145
|
+
// inside the badge) carrying either the ghost-text completion of the
|
|
146
|
+
// highlighted item or the per-prefix empty-query placeholder. One slot, one
|
|
147
|
+
// value: the suggestion wins by a plain ?? chain, so exclusivity is code,
|
|
148
|
+
// not CSS specificity. The reader and position mappers treat non-editable,
|
|
149
|
+
// non-chip elements as zero-width presentation, so the hint never reaches
|
|
150
|
+
// the model. The badge is engine-owned DOM out of JSX reach — same access
|
|
151
|
+
// pattern as the popover's anchor query — so this is a true DOM-integration
|
|
152
|
+
// effect. No deps: badge identity changes on token rewraps, which always
|
|
153
|
+
// coincide with a re-render here (query/highlight subscribed).
|
|
154
|
+
const suggestion = isActive && highlightedItem && (config?.suggestion ?? true)
|
|
155
|
+
? suggestionRemainder(query, highlightedItem.label)
|
|
156
|
+
: null;
|
|
157
|
+
const hintText = suggestion ?? (isActive && query === "" ? (config?.placeholder ?? "Type to filter") : null);
|
|
158
|
+
useIsomorphicLayoutEffect(() => {
|
|
159
|
+
const badge = store.editorRef.current?.getRootElement()?.querySelector("[data-command-badge]");
|
|
160
|
+
const existingHint = badge?.querySelector("[data-command-hint]") ?? null;
|
|
161
|
+
if (!badge || !hintText) {
|
|
162
|
+
existingHint?.remove();
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const hint = existingHint ?? createHintElement(badge);
|
|
166
|
+
if (hint.textContent !== hintText)
|
|
167
|
+
hint.textContent = hintText;
|
|
168
|
+
});
|
|
169
|
+
// Mirror the highlighted row's DOM id into the store — the editor renders
|
|
170
|
+
// it as aria-activedescendant. This is where the raw highlight index
|
|
171
|
+
// resolves against the actual item list, so the id can only be derived here.
|
|
172
|
+
// Only the ACTIVE command writes: with several Commands mounted, an inactive
|
|
173
|
+
// sibling writing null would ping-pong against the active one's id forever
|
|
174
|
+
// (max-update-depth). Clearing on close is setCommands' job.
|
|
175
|
+
useIsomorphicLayoutEffect(() => {
|
|
176
|
+
if (!isActive)
|
|
177
|
+
return;
|
|
178
|
+
store.setActiveOptionId(highlightedItem ? optionDomId(store.listboxId, highlightedItem.value) : null);
|
|
179
|
+
});
|
|
180
|
+
const selectByValue = useCallback((value) => {
|
|
181
|
+
const editor = store.editorRef.current;
|
|
182
|
+
if (!editor)
|
|
183
|
+
return;
|
|
184
|
+
const item = items.find((candidate) => candidate.value === value);
|
|
185
|
+
if (!item || item.disabled)
|
|
186
|
+
return;
|
|
187
|
+
if (kind === "insert") {
|
|
188
|
+
editor.insertChipAtTrigger({
|
|
189
|
+
prefix,
|
|
190
|
+
value: item.value,
|
|
191
|
+
label: item.label ?? item.value,
|
|
192
|
+
icon: item.icon,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
editor.deleteTrigger();
|
|
197
|
+
// Read the attachment actions lazily at selection time — their
|
|
198
|
+
// identities are store-stable, so subscribing would only re-render the
|
|
199
|
+
// list on unrelated attachment changes.
|
|
200
|
+
const { add, remove, openFileDialog } = store.getSnapshot().attachments;
|
|
201
|
+
const onSelectContext = {
|
|
202
|
+
editor: store.controller,
|
|
203
|
+
attachments: { add, remove, openFileDialog },
|
|
204
|
+
};
|
|
205
|
+
item.onSelect?.(onSelectContext);
|
|
206
|
+
}
|
|
207
|
+
editor.closeCommands();
|
|
208
|
+
}, [store, items, kind, prefix]);
|
|
209
|
+
const dismiss = useCallback(() => {
|
|
210
|
+
store.editorRef.current?.dismissCommands();
|
|
211
|
+
}, [store]);
|
|
212
|
+
// The editor's keydown handler invokes the current selection through
|
|
213
|
+
// commandSelectRef. Keep the latest values in a ref so the callback stays
|
|
214
|
+
// stable, and register it via the root's callback ref (commit-time) — not an
|
|
215
|
+
// effect, not a render-phase assignment.
|
|
216
|
+
const selectState = useAsRef({ effectiveHighlight, selectByValue, dismiss, state });
|
|
217
|
+
const runSelect = useCallback(() => {
|
|
218
|
+
const { effectiveHighlight, selectByValue, dismiss, state } = selectState.current;
|
|
219
|
+
// With a match, select it. When empty, the "No results" row is the sole
|
|
220
|
+
// option and selecting it dismisses — so Tab/Enter aren't dead (Linear-style).
|
|
221
|
+
if (effectiveHighlight)
|
|
222
|
+
selectByValue(effectiveHighlight);
|
|
223
|
+
else if (state === "empty")
|
|
224
|
+
dismiss();
|
|
225
|
+
}, []);
|
|
226
|
+
const registerSelect = useCallback((node) => {
|
|
227
|
+
store.commandSelectRef.current = node ? runSelect : null;
|
|
228
|
+
}, [store, runSelect]);
|
|
229
|
+
// Scroll the highlighted row into view as it becomes the highlight. Stable, so
|
|
230
|
+
// React calls it only on highlight change — no per-render scroll, no effect.
|
|
231
|
+
//
|
|
232
|
+
// container:"nearest" hard-scopes the scroll to the list on Chromium.
|
|
233
|
+
// Safari/Firefox silently ignore the member (unknown dictionary keys) and
|
|
234
|
+
// run an UNSCOPED block:"nearest" — scrollIntoView then scrolls every
|
|
235
|
+
// ancestor scrolling box, the window included, whenever the row isn't
|
|
236
|
+
// viewport-visible.
|
|
237
|
+
//
|
|
238
|
+
// KNOWN BUG (accepted 2026-07-10): on Safari, arrowing through the list
|
|
239
|
+
// while the popup opens can scroll the page behind it (reproduced; the
|
|
240
|
+
// positioner's viewport containment is not sufficient there). Accepted in
|
|
241
|
+
// exchange for native scroll-padding support and −24 lines; goes away when
|
|
242
|
+
// WebKit ships `container`. If it starts to hurt sooner, the scoped manual
|
|
243
|
+
// implementation lives in this function's git history (PR #39, first
|
|
244
|
+
// commit).
|
|
245
|
+
const scrollHighlightedIntoView = useCallback((node) => {
|
|
246
|
+
// Cast: lib.dom doesn't know the `container` member yet (Chromium-only).
|
|
247
|
+
node?.scrollIntoView({ block: "nearest", container: "nearest" });
|
|
248
|
+
}, []);
|
|
249
|
+
const navContext = useMemo(() => ({
|
|
250
|
+
highlightedValue: effectiveHighlight,
|
|
251
|
+
setHighlightedValue: (value) => {
|
|
252
|
+
const index = value === null ? -1 : enabledItems.findIndex((item) => item.value === value);
|
|
253
|
+
if (index >= 0)
|
|
254
|
+
store.setHighlight(index);
|
|
255
|
+
},
|
|
256
|
+
selectByValue,
|
|
257
|
+
dismiss,
|
|
258
|
+
scrollHighlightedIntoView,
|
|
259
|
+
}), [effectiveHighlight, enabledItems, selectByValue, dismiss, scrollHighlightedIntoView, store]);
|
|
260
|
+
const itemsContext = useMemo(() => ({ items, state }), [items, state]);
|
|
261
|
+
const element = useRenderElement("div", { className, render, style }, {
|
|
262
|
+
enabled: isPresent,
|
|
263
|
+
state: { loading: state === "loading", empty: state === "empty" },
|
|
264
|
+
ref: registerSelect,
|
|
265
|
+
props: [{ "data-composer-command-list": "", children }, elementProps],
|
|
266
|
+
});
|
|
267
|
+
if (!isPresent)
|
|
268
|
+
return null;
|
|
269
|
+
return (_jsx(CommandListItemsContext, { value: itemsContext, children: _jsx(CommandListNavContext, { value: navContext, children: element }) }));
|
|
270
|
+
};
|
|
271
|
+
// ---------------------------------------------------------------------------
|
|
272
|
+
// List hooks — the consumer-facing items hook and the internal nav accessor
|
|
273
|
+
// the slot primitives read.
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
export const useCommandListItems = () => {
|
|
276
|
+
const context = use(CommandListItemsContext);
|
|
277
|
+
if (!context) {
|
|
278
|
+
throw new Error("<Composer.CommandList> must be rendered inside <Composer.Command>.");
|
|
279
|
+
}
|
|
280
|
+
return context;
|
|
281
|
+
};
|
|
282
|
+
const useCommandListNav = (componentName) => {
|
|
283
|
+
const context = use(CommandListNavContext);
|
|
284
|
+
if (!context) {
|
|
285
|
+
throw new Error(`<Composer.${componentName}> must be rendered inside <Composer.Command>.`);
|
|
286
|
+
}
|
|
287
|
+
return context;
|
|
288
|
+
};
|
|
289
|
+
export const ComposerCommandList = ({ className, render, style, children: renderItem, ...elementProps }) => {
|
|
290
|
+
const { items, state } = useCommandListItems();
|
|
291
|
+
const store = useComposerContextStore();
|
|
292
|
+
return useRenderElement("div", { className, render, style }, {
|
|
293
|
+
props: [
|
|
294
|
+
{
|
|
295
|
+
// The combobox popup: the editor references this element via
|
|
296
|
+
// aria-controls and points aria-activedescendant at its rows.
|
|
297
|
+
role: "listbox",
|
|
298
|
+
id: store.listboxId,
|
|
299
|
+
"aria-label": "Suggestions",
|
|
300
|
+
"aria-busy": state === "loading" || undefined,
|
|
301
|
+
"data-composer-command-items": "",
|
|
302
|
+
children: items.map((item, index) => (_jsx(Fragment, { children: renderItem(item) }, item.value ?? `__cmd_${index}`))),
|
|
303
|
+
},
|
|
304
|
+
elementProps,
|
|
305
|
+
],
|
|
306
|
+
});
|
|
307
|
+
};
|
|
308
|
+
export const ComposerCommandLoading = ({ children, className, render, style, ...elementProps }) => useRenderElement("div", { className, render, style }, {
|
|
309
|
+
props: [{ "data-composer-command-loading": "", children }, elementProps],
|
|
310
|
+
});
|
|
311
|
+
export const ComposerCommandEmpty = ({ children, className, render, style, ...elementProps }) => useRenderElement("div", { className, render, style }, {
|
|
312
|
+
props: [{ role: "status", "data-composer-command-empty": "", children }, elementProps],
|
|
313
|
+
});
|
|
314
|
+
export const ComposerCommandDismiss = ({ children, className, render, style, ...elementProps }) => {
|
|
315
|
+
const navContext = useCommandListNav("CommandDismiss");
|
|
316
|
+
return useRenderElement("button", { className, render, style }, {
|
|
317
|
+
props: [
|
|
318
|
+
{
|
|
319
|
+
tabIndex: -1,
|
|
320
|
+
"data-composer-command-dismiss": "",
|
|
321
|
+
onMouseDown: (event) => {
|
|
322
|
+
event.preventDefault();
|
|
323
|
+
navContext.dismiss();
|
|
324
|
+
},
|
|
325
|
+
children,
|
|
326
|
+
},
|
|
327
|
+
elementProps,
|
|
328
|
+
],
|
|
329
|
+
});
|
|
330
|
+
};
|
|
331
|
+
export const ComposerCommandItem = ({ value, ...props }) => {
|
|
332
|
+
const navContext = useCommandListNav("CommandItem");
|
|
333
|
+
const itemsContext = use(CommandListItemsContext);
|
|
334
|
+
const store = useComposerContextStore();
|
|
335
|
+
const isHighlighted = navContext.highlightedValue === value;
|
|
336
|
+
// Disabled comes from the item data (CommandItemData.disabled) — the same
|
|
337
|
+
// source the nav wrap skips — so the row can never disagree with the
|
|
338
|
+
// keyboard behavior. Not a prop: two writers would let them drift.
|
|
339
|
+
const isDisabled = itemsContext?.items.some((item) => item.value === value && item.disabled) ?? false;
|
|
340
|
+
return (_jsx(Commands.Item, { ref: isHighlighted ? navContext.scrollHighlightedIntoView : undefined, id: optionDomId(store.listboxId, value), "aria-selected": isHighlighted, "data-composer-command-item": "", highlighted: isHighlighted, disabled: isDisabled, onMouseDown: (event) => {
|
|
341
|
+
// preventDefault even when disabled — the click must never steal
|
|
342
|
+
// focus from the editor.
|
|
343
|
+
event.preventDefault();
|
|
344
|
+
if (!isDisabled)
|
|
345
|
+
navContext.selectByValue(value);
|
|
346
|
+
}, onMouseEnter: isDisabled ? undefined : () => navContext.setHighlightedValue(value), ...props }));
|
|
347
|
+
};
|
|
348
|
+
export const ComposerCommandItemIcon = ({ className, render, style, ...elementProps }) => useRenderElement("span", { className, render, style }, { props: [{ "data-composer-command-item-icon": "" }, elementProps] });
|
|
349
|
+
export const ComposerCommandItemLabel = ({ className, render, style, ...elementProps }) => useRenderElement("span", { className, render, style }, { props: [{ "data-composer-command-item-label": "" }, elementProps] });
|
|
350
|
+
export const ComposerCommandItemDescription = ({ className, render, style, ...elementProps }) => useRenderElement("span", { className, render, style }, { props: [{ "data-composer-command-item-description": "" }, elementProps] });
|
|
351
|
+
// Grouping is a render concern: this reads the resolved items, buckets them by `groupBy`
|
|
352
|
+
// with `Map.groupBy` (which preserves first-appearance order — Object.groupBy would reorder
|
|
353
|
+
// integer-like keys), and renders the child once per group inside a `command-group`
|
|
354
|
+
// container. Nav order stays store-driven, so a group whose items all filter out just
|
|
355
|
+
// doesn't render. Annotate `groupBy`'s param to type the items (no inline JSX generic).
|
|
356
|
+
export const ComposerCommandGroup = ({ groupBy, children: renderGroup, }) => {
|
|
357
|
+
const { items } = useCommandListItems();
|
|
358
|
+
return (_jsx(_Fragment, { children: [...Map.groupBy(items, groupBy)].map(([group, groupItems]) => (_jsx(Commands.Group, { children: renderGroup(group, groupItems) }, group))) }));
|
|
359
|
+
};
|
|
360
|
+
export const ComposerCommandGroupLabel = ({ className, render, style, ...elementProps }) => useRenderElement("div", { className, render, style }, { props: [{ "data-composer-command-group-label": "" }, elementProps] });
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { PrimitiveProps } from "../internal/primitive-props.js";
|
|
2
|
+
type CommandsRootProps = PrimitiveProps<"div">;
|
|
3
|
+
type CommandsGroupProps = PrimitiveProps<"div">;
|
|
4
|
+
type CommandsGroupLabelProps = PrimitiveProps<"div">;
|
|
5
|
+
export type CommandsItemState = {
|
|
6
|
+
/** Present as data-highlighted while keyboard/hover highlighted. */
|
|
7
|
+
highlighted: boolean;
|
|
8
|
+
/** Present as data-disabled while the row is inert. */
|
|
9
|
+
disabled: boolean;
|
|
10
|
+
};
|
|
11
|
+
export type CommandsItemProps = PrimitiveProps<"button", CommandsItemState> & {
|
|
12
|
+
highlighted?: boolean;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
};
|
|
15
|
+
type CommandsItemLabelProps = PrimitiveProps<"span">;
|
|
16
|
+
type CommandsItemDescriptionProps = PrimitiveProps<"span">;
|
|
17
|
+
type CommandsEmptyProps = PrimitiveProps<"div">;
|
|
18
|
+
type CommandsFooterProps = PrimitiveProps<"div">;
|
|
19
|
+
export declare const Commands: (({ className, render, style, ...elementProps }: CommandsRootProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>) & {
|
|
20
|
+
Group: ({ className, render, style, ...elementProps }: CommandsGroupProps) => import("react").JSX.Element;
|
|
21
|
+
GroupLabel: ({ className, render, style, ...elementProps }: CommandsGroupLabelProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
22
|
+
Item: ({ highlighted, disabled, className, render, style, ...elementProps }: CommandsItemProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
23
|
+
ItemLabel: ({ className, render, style, ...elementProps }: CommandsItemLabelProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
24
|
+
ItemDescription: ({ className, render, style, ...elementProps }: CommandsItemDescriptionProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
25
|
+
Empty: ({ children, className, render, style, ...elementProps }: CommandsEmptyProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
26
|
+
Footer: ({ className, render, style, ...elementProps }: CommandsFooterProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
27
|
+
};
|
|
28
|
+
export {};
|