@dxos/react-ui 0.13.0 → 0.14.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/lib/Accordion.mjs +5 -6
- package/dist/lib/Accordion.mjs.map +1 -1
- package/dist/lib/Banner.mjs +2 -2
- package/dist/lib/Card.mjs +2 -2
- package/dist/lib/Combobox.mjs +1 -1
- package/dist/lib/Editable.mjs +24 -8
- package/dist/lib/Editable.mjs.map +1 -1
- package/dist/lib/Layout.mjs +1 -1
- package/dist/lib/Listbox.mjs +5 -4
- package/dist/lib/Listbox.mjs.map +1 -1
- package/dist/lib/Menu.mjs +1 -1
- package/dist/lib/Select.mjs +1 -1
- package/dist/lib/Tag.mjs +1 -1
- package/dist/lib/chunk-Button.mjs +1 -1
- package/dist/lib/chunk-Container.mjs +1 -1
- package/dist/lib/chunk-Empty.mjs +1 -1
- package/dist/lib/chunk-Input.mjs +1 -1
- package/dist/lib/chunk-Main.mjs +1 -1
- package/dist/lib/chunk-grid.mjs +2 -0
- package/dist/lib/chunk-grid.mjs.map +1 -1
- package/dist/lib/chunk-recipes.mjs +1 -0
- package/dist/lib/chunk-recipes.mjs.map +1 -1
- package/dist/lib/chunk-useThemeContext.mjs +1 -1
- package/dist/types/src/next/components/Accordion/Accordion.d.ts.map +1 -1
- package/dist/types/src/next/components/Editable/Editable.d.ts +1 -1
- package/dist/types/src/next/components/Editable/Editable.d.ts.map +1 -1
- package/dist/types/src/next/components/Listbox/Listbox.d.ts +11 -5
- package/dist/types/src/next/components/Listbox/Listbox.d.ts.map +1 -1
- package/dist/types/src/next/components/Listbox/grid.d.ts.map +1 -1
- package/dist/types/src/next/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
- package/dist/types/src/next/recipes.d.ts +1 -0
- package/dist/types/src/next/recipes.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +16 -16
- package/src/next/components/Accordion/Accordion.tsx +8 -5
- package/src/next/components/Editable/Editable.tsx +50 -22
- package/src/next/components/Listbox/Listbox.tsx +8 -5
- package/src/next/components/Listbox/grid.ts +6 -0
- package/src/next/components/Toolbar/Toolbar.stories.tsx +2 -1
- package/src/next/recipes.ts +1 -0
- package/src/next/theme/accordion.css +5 -2
- package/src/next/theme/card.css +6 -0
- package/src/next/theme/control.css +1 -3
- package/src/next/theme/editable.css +18 -0
- package/src/next/theme/row.css +4 -0
- package/src/next/theme/toggle.css +4 -0
package/dist/lib/Accordion.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-rolldown-runtime.mjs";
|
|
2
2
|
import { t as recipes } from "./chunk-recipes.mjs";
|
|
3
|
+
import { t as Block } from "./chunk-Block.mjs";
|
|
3
4
|
import { Icon } from "./Icon.mjs";
|
|
4
5
|
import { Accordion, useAccordionItemContext } from "@ark-ui/react/accordion";
|
|
5
6
|
import { forwardRef } from "react";
|
|
@@ -40,10 +41,8 @@ var AccordionItemTrigger = forwardRef(({ classNames, icon, children, ...props },
|
|
|
40
41
|
className: mx(recipes.accordionItemTrigger(), classNames),
|
|
41
42
|
ref: forwardedRef,
|
|
42
43
|
children: [
|
|
43
|
-
icon && /* @__PURE__ */ jsx(
|
|
44
|
-
|
|
45
|
-
"data-part": "item-icon",
|
|
46
|
-
className: recipes.accordionItemIcon(),
|
|
44
|
+
icon && /* @__PURE__ */ jsx(Block, {
|
|
45
|
+
classNames: recipes.accordionItemIcon(),
|
|
47
46
|
children: /* @__PURE__ */ jsx(Icon, { icon })
|
|
48
47
|
}),
|
|
49
48
|
/* @__PURE__ */ jsx("span", {
|
|
@@ -52,10 +51,10 @@ var AccordionItemTrigger = forwardRef(({ classNames, icon, children, ...props },
|
|
|
52
51
|
className: recipes.accordionItemText(),
|
|
53
52
|
children
|
|
54
53
|
}),
|
|
55
|
-
!disabled && /* @__PURE__ */ jsx(Accordion.ItemIndicator, {
|
|
54
|
+
!disabled && /* @__PURE__ */ jsx(Block, { children: /* @__PURE__ */ jsx(Accordion.ItemIndicator, {
|
|
56
55
|
className: recipes.accordionItemIndicator(),
|
|
57
56
|
children: /* @__PURE__ */ jsx(Icon, { icon: "ph--caret-right--regular" })
|
|
58
|
-
})
|
|
57
|
+
}) })
|
|
59
58
|
]
|
|
60
59
|
});
|
|
61
60
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Accordion.mjs","names":[],"sources":["../../src/next/components/Accordion/Accordion.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\n// @import-as-namespace\n\nimport { Accordion as AccordionPrimitive, useAccordionItemContext } from '@ark-ui/react/accordion';\nimport React, { forwardRef } from 'react';\n\nimport { mx } from '@dxos/ui-theme';\nimport { type ThemedClassName } from '@dxos/ui-types';\n\nimport { recipes } from '../../recipes.ts';\nimport * as Icon from '../Icon/Icon.tsx';\n\n//\n// Root\n//\n\ntype AccordionRootProps = ThemedClassName<\n Omit<AccordionPrimitive.RootProps, 'onValueChange' | 'multiple'> & {\n /** More than one item open at once (the default). */\n multiple?: boolean;\n /** A separator frame around and between the items (the default); `false` leaves only the dividers. */\n border?: boolean;\n /** Called with the open items' values. */\n onValueChange?: (value: string[]) => void;\n }\n>;\n\n/** A stack of disclosure items; any number may be open unless `multiple` is false. */\nconst AccordionRoot = forwardRef<HTMLDivElement, AccordionRootProps>(\n ({ classNames, multiple = true, border = true, onValueChange, ...props }, forwardedRef) => (\n <AccordionPrimitive.Root\n {...props}\n multiple={multiple}\n onValueChange={onValueChange && ((details) => onValueChange(details.value))}\n data-border={border ? '' : undefined}\n className={mx(recipes.accordion(), classNames)}\n ref={forwardedRef}\n />\n ),\n);\n\nAccordionRoot.displayName = 'Accordion.Root';\n\n//\n// Item\n//\n\ntype AccordionItemProps = ThemedClassName<AccordionPrimitive.ItemProps>;\n\n/** One item; `disabled` keeps the row in the list's rhythm but shows no caret and does not toggle. */\nconst AccordionItem = forwardRef<HTMLDivElement, AccordionItemProps>(({ classNames, ...props }, forwardedRef) => (\n <AccordionPrimitive.Item {...props} className={mx(recipes.accordionItem(), classNames)} ref={forwardedRef} />\n));\n\nAccordionItem.displayName = 'Accordion.Item';\n\n//\n// ItemTrigger\n//\n\ntype AccordionItemTriggerProps = ThemedClassName<AccordionPrimitive.ItemTriggerProps> & {\n /** Leading icon, before the label. */\n icon?: string;\n};\n\n/**\n * A block row: an optional leading icon, the label, and a trailing caret (Ark's `ItemIndicator`) that turns to point\n * down while open. A disabled item has nothing to open, so its caret is dropped.\n */\nconst AccordionItemTrigger = forwardRef<HTMLButtonElement, AccordionItemTriggerProps>(\n ({ classNames, icon, children, ...props }, forwardedRef) => {\n const { disabled } = useAccordionItemContext();\n return (\n <AccordionPrimitive.ItemTrigger\n {...props}\n className={mx(recipes.accordionItemTrigger(), classNames)}\n ref={forwardedRef}\n >\n {icon && (\n <
|
|
1
|
+
{"version":3,"file":"Accordion.mjs","names":[],"sources":["../../src/next/components/Accordion/Accordion.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\n// @import-as-namespace\n\nimport { Accordion as AccordionPrimitive, useAccordionItemContext } from '@ark-ui/react/accordion';\nimport React, { forwardRef } from 'react';\n\nimport { mx } from '@dxos/ui-theme';\nimport { type ThemedClassName } from '@dxos/ui-types';\n\nimport { recipes } from '../../recipes.ts';\nimport { Block } from '../Block/Block.tsx';\nimport * as Icon from '../Icon/Icon.tsx';\n\n//\n// Root\n//\n\ntype AccordionRootProps = ThemedClassName<\n Omit<AccordionPrimitive.RootProps, 'onValueChange' | 'multiple'> & {\n /** More than one item open at once (the default). */\n multiple?: boolean;\n /** A separator frame around and between the items (the default); `false` leaves only the dividers. */\n border?: boolean;\n /** Called with the open items' values. */\n onValueChange?: (value: string[]) => void;\n }\n>;\n\n/** A stack of disclosure items; any number may be open unless `multiple` is false. */\nconst AccordionRoot = forwardRef<HTMLDivElement, AccordionRootProps>(\n ({ classNames, multiple = true, border = true, onValueChange, ...props }, forwardedRef) => (\n <AccordionPrimitive.Root\n {...props}\n multiple={multiple}\n onValueChange={onValueChange && ((details) => onValueChange(details.value))}\n data-border={border ? '' : undefined}\n className={mx(recipes.accordion(), classNames)}\n ref={forwardedRef}\n />\n ),\n);\n\nAccordionRoot.displayName = 'Accordion.Root';\n\n//\n// Item\n//\n\ntype AccordionItemProps = ThemedClassName<AccordionPrimitive.ItemProps>;\n\n/** One item; `disabled` keeps the row in the list's rhythm but shows no caret and does not toggle. */\nconst AccordionItem = forwardRef<HTMLDivElement, AccordionItemProps>(({ classNames, ...props }, forwardedRef) => (\n <AccordionPrimitive.Item {...props} className={mx(recipes.accordionItem(), classNames)} ref={forwardedRef} />\n));\n\nAccordionItem.displayName = 'Accordion.Item';\n\n//\n// ItemTrigger\n//\n\ntype AccordionItemTriggerProps = ThemedClassName<AccordionPrimitive.ItemTriggerProps> & {\n /** Leading icon, before the label. */\n icon?: string;\n};\n\n/**\n * A block row: an optional leading icon, the label, and a trailing caret (Ark's `ItemIndicator`) that turns to point\n * down while open. A disabled item has nothing to open, so its caret is dropped.\n */\nconst AccordionItemTrigger = forwardRef<HTMLButtonElement, AccordionItemTriggerProps>(\n ({ classNames, icon, children, ...props }, forwardedRef) => {\n const { disabled } = useAccordionItemContext();\n return (\n <AccordionPrimitive.ItemTrigger\n {...props}\n className={mx(recipes.accordionItemTrigger(), classNames)}\n ref={forwardedRef}\n >\n {icon && (\n <Block classNames={recipes.accordionItemIcon()}>\n <Icon.Icon icon={icon} />\n </Block>\n )}\n <span data-scope='accordion' data-part='item-text' className={recipes.accordionItemText()}>\n {children}\n </span>\n {!disabled && (\n <Block>\n <AccordionPrimitive.ItemIndicator className={recipes.accordionItemIndicator()}>\n <Icon.Icon icon='ph--caret-right--regular' />\n </AccordionPrimitive.ItemIndicator>\n </Block>\n )}\n </AccordionPrimitive.ItemTrigger>\n );\n },\n);\n\nAccordionItemTrigger.displayName = 'Accordion.ItemTrigger';\n\n//\n// ItemContent\n//\n\ntype AccordionItemContentProps = ThemedClassName<AccordionPrimitive.ItemContentProps>;\n\n/** The disclosed region; its height animates from Ark's measured `--height`. */\nconst AccordionItemContent = forwardRef<HTMLDivElement, AccordionItemContentProps>(\n ({ classNames, ...props }, forwardedRef) => (\n <AccordionPrimitive.ItemContent\n {...props}\n className={mx(recipes.accordionItemContent(), classNames)}\n ref={forwardedRef}\n />\n ),\n);\n\nAccordionItemContent.displayName = 'Accordion.ItemContent';\nexport type {\n AccordionItemContentProps as ItemContentProps,\n AccordionItemProps as ItemProps,\n AccordionItemTriggerProps as ItemTriggerProps,\n AccordionRootProps as RootProps,\n};\n\nexport {\n AccordionItem as Item,\n AccordionItemContent as ItemContent,\n AccordionItemTrigger as ItemTrigger,\n AccordionRoot as Root,\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAgCA,IAAM,gBAAgB,YACnB,EAAE,YAAY,WAAW,MAAM,SAAS,MAAM,eAAe,GAAG,SAAS,iBACxE,oBAAC,UAAmB,MAApB;CACE,GAAI;CACM;CACV,eAAe,mBAAmB,YAAY,cAAc,QAAQ,KAAK;CACzE,eAAa,SAAS,KAAK,KAAA;CAC3B,WAAW,GAAG,QAAQ,UAAU,GAAG,UAAU;CAC7C,KAAK;AACN,CAAA,CAEL;AAEA,cAAc,cAAc;;AAS5B,IAAM,gBAAgB,YAAgD,EAAE,YAAY,GAAG,SAAS,iBAC9F,oBAAC,UAAmB,MAApB;CAAyB,GAAI;CAAO,WAAW,GAAG,QAAQ,cAAc,GAAG,UAAU;CAAG,KAAK;AAAe,CAAA,CAC7G;AAED,cAAc,cAAc;;;;;AAe5B,IAAM,uBAAuB,YAC1B,EAAE,YAAY,MAAM,UAAU,GAAG,SAAS,iBAAiB;CAC1D,MAAM,EAAE,aAAa,wBAAwB;CAC7C,OACE,qBAAC,UAAmB,aAApB;EACE,GAAI;EACJ,WAAW,GAAG,QAAQ,qBAAqB,GAAG,UAAU;EACxD,KAAK;EAHP,UAAA;GAKG,QACC,oBAAC,OAAD;IAAO,YAAY,QAAQ,kBAAkB;IAC3C,UAAA,oBAAC,MAAD,EAAiB,KAAO,CAAA;GACnB,CAAA;GAET,oBAAC,QAAD;IAAM,cAAW;IAAY,aAAU;IAAY,WAAW,QAAQ,kBAAkB;IACrF;GACG,CAAA;GACL,CAAC,YACA,oBAAC,OAAD,EAAA,UACE,oBAAC,UAAmB,eAApB;IAAkC,WAAW,QAAQ,uBAAuB;IAC1E,UAAA,oBAAC,MAAD,EAAW,MAAK,2BAA4B,CAAA;GACZ,CAAA,EAC7B,CAAA;EAEqB;;AAEpC,CACF;AAEA,qBAAqB,cAAc;;AASnC,IAAM,uBAAuB,YAC1B,EAAE,YAAY,GAAG,SAAS,iBACzB,oBAAC,UAAmB,aAApB;CACE,GAAI;CACJ,WAAW,GAAG,QAAQ,qBAAqB,GAAG,UAAU;CACxD,KAAK;AACN,CAAA,CAEL;AAEA,qBAAqB,cAAc"}
|
package/dist/lib/Banner.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-rolldown-runtime.mjs";
|
|
2
2
|
import { t as recipes } from "./chunk-recipes.mjs";
|
|
3
|
-
import { Icon } from "./Icon.mjs";
|
|
4
3
|
import { n as composableProps, r as slottable, t as composable } from "./chunk-slots.mjs";
|
|
4
|
+
import { t as Block } from "./chunk-Block.mjs";
|
|
5
|
+
import { Icon } from "./Icon.mjs";
|
|
5
6
|
import { n as DefaultGutterProvider, o as useInGrid, r as containerAttributes } from "./chunk-Container.mjs";
|
|
6
7
|
import { t as Button } from "./chunk-Button.mjs";
|
|
7
|
-
import { t as Block } from "./chunk-Block.mjs";
|
|
8
8
|
import { t as Typography } from "./chunk-Typography.mjs";
|
|
9
9
|
import { useId } from "react";
|
|
10
10
|
import { jsx, jsxs } from "react/jsx-runtime";
|
package/dist/lib/Card.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-rolldown-runtime.mjs";
|
|
2
2
|
import { t as recipes } from "./chunk-recipes.mjs";
|
|
3
|
-
import { Icon } from "./Icon.mjs";
|
|
4
3
|
import { n as composableProps, t as composable } from "./chunk-slots.mjs";
|
|
4
|
+
import { t as Block } from "./chunk-Block.mjs";
|
|
5
|
+
import { Icon } from "./Icon.mjs";
|
|
5
6
|
import { t as Container } from "./chunk-Container.mjs";
|
|
6
7
|
import { t as Button } from "./chunk-Button.mjs";
|
|
7
8
|
import { t as Group } from "./chunk-Group.mjs";
|
|
8
|
-
import { t as Block } from "./chunk-Block.mjs";
|
|
9
9
|
import { t as Typography } from "./chunk-Typography.mjs";
|
|
10
10
|
import { Content as MenuContent, Root as MenuRoot, Trigger as MenuTrigger } from "./Menu.mjs";
|
|
11
11
|
import { t as clickableProps } from "./chunk-clickable.mjs";
|
package/dist/lib/Combobox.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-rolldown-runtime.mjs";
|
|
2
2
|
import { t as recipes } from "./chunk-recipes.mjs";
|
|
3
|
-
import { Icon } from "./Icon.mjs";
|
|
4
3
|
import { n as composableProps, t as composable } from "./chunk-slots.mjs";
|
|
4
|
+
import { Icon } from "./Icon.mjs";
|
|
5
5
|
import { n as popupPositioning, r as usePopupSize, t as PopupScroll } from "./chunk-PopupScroll.mjs";
|
|
6
6
|
import { createContext, forwardRef, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
7
7
|
import { mx } from "@dxos/ui-theme";
|
package/dist/lib/Editable.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { t as recipes } from "./chunk-recipes.mjs";
|
|
|
3
3
|
import { Icon } from "./Icon.mjs";
|
|
4
4
|
import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
5
5
|
import { mx } from "@dxos/ui-theme";
|
|
6
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
import { createContext as createContext$1 } from "@dxos/react-hooks";
|
|
8
8
|
import { Editable, useEditable as useEditable$1, useEditableContext } from "@ark-ui/react/editable";
|
|
9
9
|
//#region src/next/components/Editable/useEditable.ts
|
|
@@ -132,9 +132,10 @@ var EDITABLE_NAME = "Editable.Root";
|
|
|
132
132
|
var EDITABLE_PREVIEW_NAME = "Editable.Preview";
|
|
133
133
|
var EDITABLE_INPUT_NAME = "Editable.Input";
|
|
134
134
|
var [EditableActivationProvider, useEditableActivation] = createContext$1(EDITABLE_NAME);
|
|
135
|
+
var [EditableCommitProvider, useEditableCommit] = createContext$1(EDITABLE_NAME);
|
|
135
136
|
/** Seeds the machine (through `useEditable`) and lays Preview and Input into one grid cell, so the swap moves nothing. */
|
|
136
137
|
var EditableRoot = forwardRef(({ children, classNames, value, defaultValue, onValueChange, activation, blurBehavior, disabled, placeholder, editing, onEditingChange, ...props }, forwardedRef) => {
|
|
137
|
-
const { api, activationProps } = useEditable({
|
|
138
|
+
const { api, activationProps, commit } = useEditable({
|
|
138
139
|
value,
|
|
139
140
|
defaultValue,
|
|
140
141
|
onValueChange,
|
|
@@ -152,14 +153,17 @@ var EditableRoot = forwardRef(({ children, classNames, value, defaultValue, onVa
|
|
|
152
153
|
ref: forwardedRef,
|
|
153
154
|
children: /* @__PURE__ */ jsx(EditableActivationProvider, {
|
|
154
155
|
...activationProps,
|
|
155
|
-
children
|
|
156
|
+
children: /* @__PURE__ */ jsx(EditableCommitProvider, {
|
|
157
|
+
commit,
|
|
158
|
+
children
|
|
159
|
+
})
|
|
156
160
|
})
|
|
157
161
|
});
|
|
158
162
|
});
|
|
159
163
|
EditableRoot.displayName = EDITABLE_NAME;
|
|
160
164
|
/** The static text and the activation affordance; hidden by the machine while editing. */
|
|
161
165
|
var EditablePreview = forwardRef(({ classNames, ...props }, forwardedRef) => {
|
|
162
|
-
const { valueText } = useEditableContext();
|
|
166
|
+
const { valueText, edit } = useEditableContext();
|
|
163
167
|
const { role, onKeyDown } = useEditableActivation(EDITABLE_PREVIEW_NAME);
|
|
164
168
|
return /* @__PURE__ */ jsxs(Editable.Preview, {
|
|
165
169
|
"aria-label": void 0,
|
|
@@ -173,14 +177,20 @@ var EditablePreview = forwardRef(({ classNames, ...props }, forwardedRef) => {
|
|
|
173
177
|
children: valueText
|
|
174
178
|
}), /* @__PURE__ */ jsx(Icon, {
|
|
175
179
|
icon: "ph--pencil-simple--regular",
|
|
176
|
-
classNames: recipes.editablePreviewIcon()
|
|
180
|
+
classNames: recipes.editablePreviewIcon(),
|
|
181
|
+
"data-testid": "editable.edit",
|
|
182
|
+
onClick: (event) => {
|
|
183
|
+
event.stopPropagation();
|
|
184
|
+
edit();
|
|
185
|
+
}
|
|
177
186
|
})]
|
|
178
187
|
});
|
|
179
188
|
});
|
|
180
189
|
EditablePreview.displayName = EDITABLE_PREVIEW_NAME;
|
|
181
|
-
/** The field shown while editing
|
|
190
|
+
/** The field shown while editing, focused with the caret at the end, with a save button at its end. */
|
|
182
191
|
var EditableInput = forwardRef(({ classNames, ...props }, forwardedRef) => {
|
|
183
192
|
const { editing } = useEditableContext();
|
|
193
|
+
const { commit } = useEditableCommit(EDITABLE_INPUT_NAME);
|
|
184
194
|
const localRef = useRef(null);
|
|
185
195
|
useEffect(() => {
|
|
186
196
|
const element = localRef.current;
|
|
@@ -190,7 +200,7 @@ var EditableInput = forwardRef(({ classNames, ...props }, forwardedRef) => {
|
|
|
190
200
|
element.setSelectionRange(end, end);
|
|
191
201
|
}
|
|
192
202
|
}, [editing]);
|
|
193
|
-
return /* @__PURE__ */ jsx(Editable.Input, {
|
|
203
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Editable.Input, {
|
|
194
204
|
...props,
|
|
195
205
|
className: mx(recipes.editableInput(), classNames),
|
|
196
206
|
ref: (element) => {
|
|
@@ -202,7 +212,13 @@ var EditableInput = forwardRef(({ classNames, ...props }, forwardedRef) => {
|
|
|
202
212
|
if (typeof cleanup === "function") cleanup();
|
|
203
213
|
};
|
|
204
214
|
}
|
|
205
|
-
})
|
|
215
|
+
}), /* @__PURE__ */ jsx(Editable.SubmitTrigger, {
|
|
216
|
+
className: recipes.editableSubmit(),
|
|
217
|
+
"aria-label": "Save",
|
|
218
|
+
"data-testid": "editable.save",
|
|
219
|
+
onClick: () => commit(),
|
|
220
|
+
children: /* @__PURE__ */ jsx(Icon, { icon: "ph--check--bold" })
|
|
221
|
+
})] });
|
|
206
222
|
});
|
|
207
223
|
EditableInput.displayName = EDITABLE_INPUT_NAME;
|
|
208
224
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Editable.mjs","names":[],"sources":["../../src/next/components/Editable/useEditable.ts","../../src/next/components/Editable/Editable.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport { type UseEditableReturn as EditableApi, useEditable as useMachine } from '@ark-ui/react/editable';\nimport { type KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';\n\n// The inline-edit state machine — `@ark-ui/react`'s Editable (zag) — behind DXOS's own contract.\n//\n// `Editable` renders an `<input>`, but a markdown field wants a CodeMirror editor and a rendered\n// preview — and those live in packages that depend on this one, so they cannot be reached from\n// here. Both flavours therefore share the behaviour through this hook rather than the component:\n// one definition of what commits, what reverts, and what opens.\n\n/** What turns the preview into an editor. `dblclick` suits rows whose single click already selects. */\nexport type EditableActivation = 'click' | 'dblclick' | 'focus' | 'none';\n\n/** What an interaction outside does with the pending edit. `Escape` always reverts and `Enter` always commits. */\nexport type EditableBlurBehavior = 'commit' | 'revert';\n\nexport type UseEditableOptions = {\n /** Current text (controlled). */\n value?: string;\n /** Initial text when uncontrolled. */\n defaultValue?: string;\n /** Called when an edit is committed — never while typing, so a keystroke is not a write. */\n onValueChange?: (value: string) => void;\n activation?: EditableActivation;\n blurBehavior?: EditableBlurBehavior;\n disabled?: boolean;\n /** Shown, dimmed, when the value is empty. */\n placeholder?: string;\n /** Editing state (controlled); pair with `onEditingChange` to drive it from outside. */\n editing?: boolean;\n onEditingChange?: (editing: boolean) => void;\n};\n\n/** Props for whatever draws the resting state, so every flavour opens the same ways. */\nexport type EditablePreviewBinding = ReturnType<EditableApi['getPreviewProps']>;\n\n/**\n * What the preview needs beyond the machine's own props. Empty when the machine already answers the\n * keyboard on its own.\n */\nexport type EditableActivationBinding = {\n role?: 'button';\n onKeyDown?: (event: KeyboardEvent) => void;\n};\n\nexport type UseEditableReturn = {\n value: string;\n /** The pending text; separate from `value` so `Escape` has something to revert to. */\n draft: string;\n editing: boolean;\n disabled: boolean;\n setDraft: (draft: string) => void;\n edit: () => void;\n /** Commits `next` when given: a caller that already holds the text must not race the draft state. */\n commit: (next?: string) => void;\n revert: () => void;\n previewProps: EditablePreviewBinding;\n /**\n * The keyboard door, for a part that renders the machine's own preview and so already has the\n * rest of {@link UseEditableReturn.previewProps} applied to it.\n */\n activationProps: EditableActivationBinding;\n /** The machine itself, for the parts that render Ark's own anatomy. */\n api: EditableApi;\n};\n\n/**\n * Inline edit: text that becomes editable in place, with a committed value distinct from the\n * pending one.\n *\n * The machine owns the pending text, which state the field is in, the activation gesture, `Enter` /\n * `Escape`, and what an interaction outside the field does with the edit. This owns the thing it has\n * no notion of — the committed value — so `onValueChange` fires when an edit lands rather than on\n * every keystroke.\n *\n * **The value.** Controlled with `value`, uncontrolled with `defaultValue`; `draft` is the pending\n * text either way and only ever reaches `onValueChange` on a commit. A controlled host that does not\n * echo a commit back is refusing it, and the field returns to the value the host still holds.\n *\n * **Who owns the edit state.** Left alone, the machine does: the field opens on `activation` and\n * closes on `Enter`, `Escape`, or an interaction outside it. Passing `editing` takes that ownership,\n * which is what a pane editor does — the pane IS the editor, so it is held open and never closes.\n * The machine then treats a submit as a request to whoever holds the prop and announces only the\n * state change, so `commit` and `revert` here act on it directly rather than waiting to be told.\n * Either way an edit is delivered exactly once.\n *\n * **Commit and revert.** `commit` writes the pending text — or the text it is handed, for a caller\n * that already holds it and must not race the draft. `revert` restores the last committed text and\n * discards the rest, including on a field that was empty when it opened, which the machine alone\n * would leave holding the discarded text.\n */\nexport const useEditable = ({\n value: valueProp,\n defaultValue = '',\n onValueChange,\n activation = 'click',\n blurBehavior = 'commit',\n disabled = false,\n placeholder,\n editing: editingProp,\n onEditingChange,\n}: UseEditableOptions): UseEditableReturn => {\n const [valueState, setValueState] = useState(defaultValue);\n const value = valueProp ?? valueState;\n\n // The machine's callbacks are handed to it once, so they read the committed text through a ref\n // rather than closing over whichever render created them.\n const committed = useRef(value);\n committed.current = value;\n const apiRef = useRef<EditableApi | null>(null);\n const onValueChangeRef = useRef(onValueChange);\n onValueChangeRef.current = onValueChange;\n\n /**\n * The one way an edit lands, whether the machine announced it or a caller asked for it directly.\n *\n * Idempotent on the text it already holds, so the two routes can both run in a tick without\n * writing twice: `committed` is advanced here rather than waiting for the render that reads the\n * prop back.\n */\n const deliver = useCallback((next: string) => {\n if (next === committed.current) {\n return;\n }\n committed.current = next;\n setValueState(next);\n onValueChangeRef.current?.(next);\n }, []);\n\n const api = useMachine({\n defaultValue: value,\n placeholder,\n activationMode: activation,\n submitMode: blurBehavior === 'commit' ? 'both' : 'enter',\n // Caret at the end rather than a selection: opening a title is usually the start of amending it,\n // and a select-all turns the next keystroke into a silent delete of the whole value.\n selectOnFocus: false,\n disabled,\n edit: editingProp,\n onEditChange: ({ edit }) => onEditingChange?.(edit),\n onValueCommit: ({ value: next }) => deliver(next),\n // The machine's own revert keeps whatever it holds when the text it opened with was empty, so\n // `Escape` on a field showing its placeholder would keep the discarded text instead. Restored\n // inside the machine's own transition rather than left to the reconciling effect below, which\n // would catch it a render later — after the discarded text had been drawn once.\n onValueRevert: () => apiRef.current?.setValue(committed.current),\n });\n apiRef.current = api;\n\n // A value that changed elsewhere — a peer's edit, a controlled parent refusing a commit — has to\n // reach the machine, which otherwise holds the one it was seeded with. Never mid-edit: that would\n // overwrite what is being typed.\n useEffect(() => {\n if (!api.editing && api.value !== value) {\n api.setValue(value);\n }\n }, [api, value]);\n\n const setDraft = useCallback((draft: string) => api.setValue(draft), [api]);\n\n /**\n * Commits, and delivers the edit rather than trusting the machine to announce it.\n *\n * A field whose editing state is controlled never leaves edit on its own, so the machine treats\n * `SUBMIT` as a request to the host and reports only the state change — `onValueCommit` never\n * runs. A pane held open that way is the whole point of driving `editing` from outside, and its\n * text would never be written.\n */\n const commit = useCallback(\n (next?: string) => {\n const text = next ?? api.value;\n api.setValue(text);\n api.submit();\n deliver(text);\n },\n [api, deliver],\n );\n\n /** Reverts for the same reason `commit` delivers: a controlled field never reaches the machine's own cancel. */\n const revert = useCallback(() => {\n api.setValue(committed.current);\n api.cancel();\n }, [api]);\n\n // A gesture the machine only takes from a pointer needs the same door for a keyboard reader. Its\n // own keyboard answer is `focus` activation, which is wrong for a field a single click opens —\n // tabbing across a list of them would put every row into edit on the way past.\n const activationProps = useMemo<EditableActivationBinding>(() => {\n if (activation !== 'click' && activation !== 'dblclick') {\n return {};\n }\n\n return {\n role: 'button',\n onKeyDown: (event: KeyboardEvent) => {\n if (!event.defaultPrevented && (event.key === 'Enter' || event.key === ' ')) {\n event.preventDefault();\n api.edit();\n }\n },\n };\n }, [activation, api]);\n\n const previewProps = useMemo<EditablePreviewBinding>(\n () => ({\n ...api.getPreviewProps(),\n // The machine names the preview \"edit\", which is what the gesture does rather than what the\n // field holds — in a list of rows that is every row with the same name.\n 'aria-label': undefined,\n ...activationProps,\n }),\n [api, activationProps],\n );\n\n return {\n value,\n draft: api.value,\n editing: api.editing,\n disabled,\n setDraft,\n edit: api.edit,\n commit,\n revert,\n previewProps,\n activationProps,\n api,\n };\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\n// @import-as-namespace\n\nimport { Editable as EditablePrimitive, useEditableContext } from '@ark-ui/react/editable';\nimport React, { type ComponentPropsWithRef, type PropsWithChildren, forwardRef, useEffect, useRef } from 'react';\n\nimport { createContext } from '@dxos/react-hooks';\nimport { mx } from '@dxos/ui-theme';\nimport { type ThemedClassName } from '@dxos/ui-types';\n\nimport { recipes } from '../../recipes.ts';\nimport * as Icon from '../Icon/Icon.tsx';\nimport { type EditableActivationBinding, type UseEditableOptions, useEditable } from './useEditable.ts';\n\nconst EDITABLE_NAME = 'Editable.Root';\nconst EDITABLE_PREVIEW_NAME = 'Editable.Preview';\nconst EDITABLE_INPUT_NAME = 'Editable.Input';\n\n// The keyboard door onto the preview depends on the activation gesture, which the root is given and the machine the\n// parts read does not expose.\nconst [EditableActivationProvider, useEditableActivation] = createContext<EditableActivationBinding>(EDITABLE_NAME);\n\n//\n// Root\n//\n\ntype EditableRootProps = ThemedClassName<\n PropsWithChildren<UseEditableOptions & Omit<ComponentPropsWithRef<'div'>, keyof UseEditableOptions | 'children'>>\n>;\n\n/** Seeds the machine (through `useEditable`) and lays Preview and Input into one grid cell, so the swap moves nothing. */\nconst EditableRoot = forwardRef<HTMLDivElement, EditableRootProps>(\n (\n {\n children,\n classNames,\n value,\n defaultValue,\n onValueChange,\n activation,\n blurBehavior,\n disabled,\n placeholder,\n editing,\n onEditingChange,\n ...props\n },\n forwardedRef,\n ) => {\n const { api, activationProps } = useEditable({\n value,\n defaultValue,\n onValueChange,\n activation,\n blurBehavior,\n disabled,\n placeholder,\n editing,\n onEditingChange,\n });\n\n return (\n <EditablePrimitive.RootProvider\n {...props}\n value={api}\n className={mx(recipes.editable(), classNames)}\n ref={forwardedRef}\n >\n <EditableActivationProvider {...activationProps}>{children}</EditableActivationProvider>\n </EditablePrimitive.RootProvider>\n );\n },\n);\n\nEditableRoot.displayName = EDITABLE_NAME;\n\n//\n// Preview\n//\n\ntype EditablePreviewProps = ThemedClassName<Omit<ComponentPropsWithRef<'span'>, 'children'>>;\n\n/** The static text and the activation affordance; hidden by the machine while editing. */\nconst EditablePreview = forwardRef<HTMLSpanElement, EditablePreviewProps>(({ classNames, ...props }, forwardedRef) => {\n const { valueText } = useEditableContext();\n const { role, onKeyDown } = useEditableActivation(EDITABLE_PREVIEW_NAME);\n\n return (\n <EditablePrimitive.Preview\n // Ahead of the spread so a caller can still name it: the machine names it \"edit\", what the gesture does rather\n // than what the field holds, which in a list of rows is every row's name.\n aria-label={undefined}\n // The machine opens on a pointer gesture only; without this the preview is a tab stop that answers nothing.\n role={role}\n onKeyDown={onKeyDown}\n {...props}\n className={mx(recipes.editablePreview(), classNames)}\n ref={forwardedRef}\n >\n <span className={recipes.editablePreviewText()}>{valueText}</span>\n <Icon.Icon icon='ph--pencil-simple--regular' classNames={recipes.editablePreviewIcon()} />\n </EditablePrimitive.Preview>\n );\n});\n\nEditablePreview.displayName = EDITABLE_PREVIEW_NAME;\n\n//\n// Input\n//\n\ntype EditableInputProps = ThemedClassName<Omit<ComponentPropsWithRef<'input'>, 'value' | 'onChange' | 'placeholder'>>;\n\n/** The field shown while editing; focused with the caret at the end. */\nconst EditableInput = forwardRef<HTMLInputElement, EditableInputProps>(({ classNames, ...props }, forwardedRef) => {\n const { editing } = useEditableContext();\n const localRef = useRef<HTMLInputElement | null>(null);\n\n // The machine focuses the input but leaves the caret where the browser puts it, which from the keyboard is the start.\n useEffect(() => {\n const element = localRef.current;\n if (editing && element) {\n element.focus();\n const end = element.value.length;\n element.setSelectionRange(end, end);\n }\n }, [editing]);\n\n return (\n <EditablePrimitive.Input\n {...props}\n className={mx(recipes.editableInput(), classNames)}\n ref={(element) => {\n localRef.current = element;\n // Typed `unknown`: `ForwardedRef`'s callback is declared void, but React 19 may return a cleanup.\n const cleanup: unknown = typeof forwardedRef === 'function' ? forwardedRef(element) : undefined;\n if (forwardedRef && typeof forwardedRef !== 'function') {\n forwardedRef.current = element;\n }\n return () => {\n localRef.current = null;\n if (typeof cleanup === 'function') {\n cleanup();\n }\n };\n }}\n />\n );\n});\n\nEditableInput.displayName = EDITABLE_INPUT_NAME;\n\nexport { EditableInput as Input, EditablePreview as Preview, EditableRoot as Root };\nexport type { EditableInputProps as InputProps, EditablePreviewProps as PreviewProps, EditableRootProps as RootProps };\nexport * from './useEditable.ts';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Editable.mjs","names":[],"sources":["../../src/next/components/Editable/useEditable.ts","../../src/next/components/Editable/Editable.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport { type UseEditableReturn as EditableApi, useEditable as useMachine } from '@ark-ui/react/editable';\nimport { type KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';\n\n// The inline-edit state machine — `@ark-ui/react`'s Editable (zag) — behind DXOS's own contract.\n//\n// `Editable` renders an `<input>`, but a markdown field wants a CodeMirror editor and a rendered\n// preview — and those live in packages that depend on this one, so they cannot be reached from\n// here. Both flavours therefore share the behaviour through this hook rather than the component:\n// one definition of what commits, what reverts, and what opens.\n\n/** What turns the preview into an editor. `dblclick` suits rows whose single click already selects. */\nexport type EditableActivation = 'click' | 'dblclick' | 'focus' | 'none';\n\n/** What an interaction outside does with the pending edit. `Escape` always reverts and `Enter` always commits. */\nexport type EditableBlurBehavior = 'commit' | 'revert';\n\nexport type UseEditableOptions = {\n /** Current text (controlled). */\n value?: string;\n /** Initial text when uncontrolled. */\n defaultValue?: string;\n /** Called when an edit is committed — never while typing, so a keystroke is not a write. */\n onValueChange?: (value: string) => void;\n activation?: EditableActivation;\n blurBehavior?: EditableBlurBehavior;\n disabled?: boolean;\n /** Shown, dimmed, when the value is empty. */\n placeholder?: string;\n /** Editing state (controlled); pair with `onEditingChange` to drive it from outside. */\n editing?: boolean;\n onEditingChange?: (editing: boolean) => void;\n};\n\n/** Props for whatever draws the resting state, so every flavour opens the same ways. */\nexport type EditablePreviewBinding = ReturnType<EditableApi['getPreviewProps']>;\n\n/**\n * What the preview needs beyond the machine's own props. Empty when the machine already answers the\n * keyboard on its own.\n */\nexport type EditableActivationBinding = {\n role?: 'button';\n onKeyDown?: (event: KeyboardEvent) => void;\n};\n\nexport type UseEditableReturn = {\n value: string;\n /** The pending text; separate from `value` so `Escape` has something to revert to. */\n draft: string;\n editing: boolean;\n disabled: boolean;\n setDraft: (draft: string) => void;\n edit: () => void;\n /** Commits `next` when given: a caller that already holds the text must not race the draft state. */\n commit: (next?: string) => void;\n revert: () => void;\n previewProps: EditablePreviewBinding;\n /**\n * The keyboard door, for a part that renders the machine's own preview and so already has the\n * rest of {@link UseEditableReturn.previewProps} applied to it.\n */\n activationProps: EditableActivationBinding;\n /** The machine itself, for the parts that render Ark's own anatomy. */\n api: EditableApi;\n};\n\n/**\n * Inline edit: text that becomes editable in place, with a committed value distinct from the\n * pending one.\n *\n * The machine owns the pending text, which state the field is in, the activation gesture, `Enter` /\n * `Escape`, and what an interaction outside the field does with the edit. This owns the thing it has\n * no notion of — the committed value — so `onValueChange` fires when an edit lands rather than on\n * every keystroke.\n *\n * **The value.** Controlled with `value`, uncontrolled with `defaultValue`; `draft` is the pending\n * text either way and only ever reaches `onValueChange` on a commit. A controlled host that does not\n * echo a commit back is refusing it, and the field returns to the value the host still holds.\n *\n * **Who owns the edit state.** Left alone, the machine does: the field opens on `activation` and\n * closes on `Enter`, `Escape`, or an interaction outside it. Passing `editing` takes that ownership,\n * which is what a pane editor does — the pane IS the editor, so it is held open and never closes.\n * The machine then treats a submit as a request to whoever holds the prop and announces only the\n * state change, so `commit` and `revert` here act on it directly rather than waiting to be told.\n * Either way an edit is delivered exactly once.\n *\n * **Commit and revert.** `commit` writes the pending text — or the text it is handed, for a caller\n * that already holds it and must not race the draft. `revert` restores the last committed text and\n * discards the rest, including on a field that was empty when it opened, which the machine alone\n * would leave holding the discarded text.\n */\nexport const useEditable = ({\n value: valueProp,\n defaultValue = '',\n onValueChange,\n activation = 'click',\n blurBehavior = 'commit',\n disabled = false,\n placeholder,\n editing: editingProp,\n onEditingChange,\n}: UseEditableOptions): UseEditableReturn => {\n const [valueState, setValueState] = useState(defaultValue);\n const value = valueProp ?? valueState;\n\n // The machine's callbacks are handed to it once, so they read the committed text through a ref\n // rather than closing over whichever render created them.\n const committed = useRef(value);\n committed.current = value;\n const apiRef = useRef<EditableApi | null>(null);\n const onValueChangeRef = useRef(onValueChange);\n onValueChangeRef.current = onValueChange;\n\n /**\n * The one way an edit lands, whether the machine announced it or a caller asked for it directly.\n *\n * Idempotent on the text it already holds, so the two routes can both run in a tick without\n * writing twice: `committed` is advanced here rather than waiting for the render that reads the\n * prop back.\n */\n const deliver = useCallback((next: string) => {\n if (next === committed.current) {\n return;\n }\n committed.current = next;\n setValueState(next);\n onValueChangeRef.current?.(next);\n }, []);\n\n const api = useMachine({\n defaultValue: value,\n placeholder,\n activationMode: activation,\n submitMode: blurBehavior === 'commit' ? 'both' : 'enter',\n // Caret at the end rather than a selection: opening a title is usually the start of amending it,\n // and a select-all turns the next keystroke into a silent delete of the whole value.\n selectOnFocus: false,\n disabled,\n edit: editingProp,\n onEditChange: ({ edit }) => onEditingChange?.(edit),\n onValueCommit: ({ value: next }) => deliver(next),\n // The machine's own revert keeps whatever it holds when the text it opened with was empty, so\n // `Escape` on a field showing its placeholder would keep the discarded text instead. Restored\n // inside the machine's own transition rather than left to the reconciling effect below, which\n // would catch it a render later — after the discarded text had been drawn once.\n onValueRevert: () => apiRef.current?.setValue(committed.current),\n });\n apiRef.current = api;\n\n // A value that changed elsewhere — a peer's edit, a controlled parent refusing a commit — has to\n // reach the machine, which otherwise holds the one it was seeded with. Never mid-edit: that would\n // overwrite what is being typed.\n useEffect(() => {\n if (!api.editing && api.value !== value) {\n api.setValue(value);\n }\n }, [api, value]);\n\n const setDraft = useCallback((draft: string) => api.setValue(draft), [api]);\n\n /**\n * Commits, and delivers the edit rather than trusting the machine to announce it.\n *\n * A field whose editing state is controlled never leaves edit on its own, so the machine treats\n * `SUBMIT` as a request to the host and reports only the state change — `onValueCommit` never\n * runs. A pane held open that way is the whole point of driving `editing` from outside, and its\n * text would never be written.\n */\n const commit = useCallback(\n (next?: string) => {\n const text = next ?? api.value;\n api.setValue(text);\n api.submit();\n deliver(text);\n },\n [api, deliver],\n );\n\n /** Reverts for the same reason `commit` delivers: a controlled field never reaches the machine's own cancel. */\n const revert = useCallback(() => {\n api.setValue(committed.current);\n api.cancel();\n }, [api]);\n\n // A gesture the machine only takes from a pointer needs the same door for a keyboard reader. Its\n // own keyboard answer is `focus` activation, which is wrong for a field a single click opens —\n // tabbing across a list of them would put every row into edit on the way past.\n const activationProps = useMemo<EditableActivationBinding>(() => {\n if (activation !== 'click' && activation !== 'dblclick') {\n return {};\n }\n\n return {\n role: 'button',\n onKeyDown: (event: KeyboardEvent) => {\n if (!event.defaultPrevented && (event.key === 'Enter' || event.key === ' ')) {\n event.preventDefault();\n api.edit();\n }\n },\n };\n }, [activation, api]);\n\n const previewProps = useMemo<EditablePreviewBinding>(\n () => ({\n ...api.getPreviewProps(),\n // The machine names the preview \"edit\", which is what the gesture does rather than what the\n // field holds — in a list of rows that is every row with the same name.\n 'aria-label': undefined,\n ...activationProps,\n }),\n [api, activationProps],\n );\n\n return {\n value,\n draft: api.value,\n editing: api.editing,\n disabled,\n setDraft,\n edit: api.edit,\n commit,\n revert,\n previewProps,\n activationProps,\n api,\n };\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\n// @import-as-namespace\n\nimport { Editable as EditablePrimitive, useEditableContext } from '@ark-ui/react/editable';\nimport React, { type ComponentPropsWithRef, type PropsWithChildren, forwardRef, useEffect, useRef } from 'react';\n\nimport { createContext } from '@dxos/react-hooks';\nimport { mx } from '@dxos/ui-theme';\nimport { type ThemedClassName } from '@dxos/ui-types';\n\nimport { recipes } from '../../recipes.ts';\nimport * as Icon from '../Icon/Icon.tsx';\nimport { type EditableActivationBinding, type UseEditableOptions, useEditable } from './useEditable.ts';\n\nconst EDITABLE_NAME = 'Editable.Root';\nconst EDITABLE_PREVIEW_NAME = 'Editable.Preview';\nconst EDITABLE_INPUT_NAME = 'Editable.Input';\n\n// The keyboard door onto the preview depends on the activation gesture, which the root is given and the machine the\n// parts read does not expose.\nconst [EditableActivationProvider, useEditableActivation] = createContext<EditableActivationBinding>(EDITABLE_NAME);\n\n// A controlled field's submit never reaches the machine's commit callback, so Save commits through the hook.\nconst [EditableCommitProvider, useEditableCommit] = createContext<{ commit: () => void }>(EDITABLE_NAME);\n\n//\n// Root\n//\n\ntype EditableRootProps = ThemedClassName<\n PropsWithChildren<UseEditableOptions & Omit<ComponentPropsWithRef<'div'>, keyof UseEditableOptions | 'children'>>\n>;\n\n/** Seeds the machine (through `useEditable`) and lays Preview and Input into one grid cell, so the swap moves nothing. */\nconst EditableRoot = forwardRef<HTMLDivElement, EditableRootProps>(\n (\n {\n children,\n classNames,\n value,\n defaultValue,\n onValueChange,\n activation,\n blurBehavior,\n disabled,\n placeholder,\n editing,\n onEditingChange,\n ...props\n },\n forwardedRef,\n ) => {\n const { api, activationProps, commit } = useEditable({\n value,\n defaultValue,\n onValueChange,\n activation,\n blurBehavior,\n disabled,\n placeholder,\n editing,\n onEditingChange,\n });\n\n return (\n <EditablePrimitive.RootProvider\n {...props}\n value={api}\n className={mx(recipes.editable(), classNames)}\n ref={forwardedRef}\n >\n <EditableActivationProvider {...activationProps}>\n <EditableCommitProvider commit={commit}>{children}</EditableCommitProvider>\n </EditableActivationProvider>\n </EditablePrimitive.RootProvider>\n );\n },\n);\n\nEditableRoot.displayName = EDITABLE_NAME;\n\n//\n// Preview\n//\n\ntype EditablePreviewProps = ThemedClassName<Omit<ComponentPropsWithRef<'span'>, 'children'>>;\n\n/** The static text and the activation affordance; hidden by the machine while editing. */\nconst EditablePreview = forwardRef<HTMLSpanElement, EditablePreviewProps>(({ classNames, ...props }, forwardedRef) => {\n const { valueText, edit } = useEditableContext();\n const { role, onKeyDown } = useEditableActivation(EDITABLE_PREVIEW_NAME);\n\n return (\n <EditablePrimitive.Preview\n // Ahead of the spread so a caller can still name it: the machine names it \"edit\", what the gesture does rather\n // than what the field holds, which in a list of rows is every row's name.\n aria-label={undefined}\n // The machine opens on a pointer gesture only; without this the preview is a tab stop that answers nothing.\n role={role}\n onKeyDown={onKeyDown}\n {...props}\n className={mx(recipes.editablePreview(), classNames)}\n ref={forwardedRef}\n >\n <span className={recipes.editablePreviewText()}>{valueText}</span>\n {/* The pencil is a door of its own: one click opens the field whatever gesture the preview takes (a double-click\n in a list, where a single click selects the row). */}\n <Icon.Icon\n icon='ph--pencil-simple--regular'\n classNames={recipes.editablePreviewIcon()}\n data-testid='editable.edit'\n // A disabled field hides the pencil (`editable.css`), so it takes no click.\n onClick={(event) => {\n event.stopPropagation();\n edit();\n }}\n />\n </EditablePrimitive.Preview>\n );\n});\n\nEditablePreview.displayName = EDITABLE_PREVIEW_NAME;\n\n//\n// Input\n//\n\ntype EditableInputProps = ThemedClassName<Omit<ComponentPropsWithRef<'input'>, 'value' | 'onChange' | 'placeholder'>>;\n\n/** The field shown while editing, focused with the caret at the end, with a save button at its end. */\nconst EditableInput = forwardRef<HTMLInputElement, EditableInputProps>(({ classNames, ...props }, forwardedRef) => {\n const { editing } = useEditableContext();\n const { commit } = useEditableCommit(EDITABLE_INPUT_NAME);\n const localRef = useRef<HTMLInputElement | null>(null);\n\n // The machine focuses the input but leaves the caret where the browser puts it, which from the keyboard is the start.\n useEffect(() => {\n const element = localRef.current;\n if (editing && element) {\n element.focus();\n const end = element.value.length;\n element.setSelectionRange(end, end);\n }\n }, [editing]);\n\n return (\n <>\n <EditablePrimitive.Input\n {...props}\n className={mx(recipes.editableInput(), classNames)}\n ref={(element) => {\n localRef.current = element;\n // Typed `unknown`: `ForwardedRef`'s callback is declared void, but React 19 may return a cleanup.\n const cleanup: unknown = typeof forwardedRef === 'function' ? forwardedRef(element) : undefined;\n if (forwardedRef && typeof forwardedRef !== 'function') {\n forwardedRef.current = element;\n }\n return () => {\n localRef.current = null;\n if (typeof cleanup === 'function') {\n cleanup();\n }\n };\n }}\n />\n {/* Saves the edit, at the input's end in the same cell; the machine hides it while not editing. */}\n <EditablePrimitive.SubmitTrigger\n className={recipes.editableSubmit()}\n aria-label='Save'\n data-testid='editable.save'\n onClick={() => commit()}\n >\n <Icon.Icon icon='ph--check--bold' />\n </EditablePrimitive.SubmitTrigger>\n </>\n );\n});\n\nEditableInput.displayName = EDITABLE_INPUT_NAME;\n\nexport { EditableInput as Input, EditablePreview as Preview, EditableRoot as Root };\nexport type { EditableInputProps as InputProps, EditablePreviewProps as PreviewProps, EditableRootProps as RootProps };\nexport * from './useEditable.ts';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+FA,IAAa,eAAe,EAC1B,OAAO,WACP,eAAe,IACf,eACA,aAAa,SACb,eAAe,UACf,WAAW,OACX,aACA,SAAS,aACT,sBAC2C;CAC3C,MAAM,CAAC,YAAY,iBAAiB,SAAS,YAAY;CACzD,MAAM,QAAQ,aAAa;CAI3B,MAAM,YAAY,OAAO,KAAK;CAC9B,UAAU,UAAU;CACpB,MAAM,SAAS,OAA2B,IAAI;CAC9C,MAAM,mBAAmB,OAAO,aAAa;CAC7C,iBAAiB,UAAU;;;;;;;;CAS3B,MAAM,UAAU,aAAa,SAAiB;EAC5C,IAAI,SAAS,UAAU,SACrB;EAEF,UAAU,UAAU;EACpB,cAAc,IAAI;EAClB,iBAAiB,UAAU,IAAI;CACjC,GAAG,CAAC,CAAC;CAEL,MAAM,MAAM,cAAW;EACrB,cAAc;EACd;EACA,gBAAgB;EAChB,YAAY,iBAAiB,WAAW,SAAS;EAGjD,eAAe;EACf;EACA,MAAM;EACN,eAAe,EAAE,WAAW,kBAAkB,IAAI;EAClD,gBAAgB,EAAE,OAAO,WAAW,QAAQ,IAAI;EAKhD,qBAAqB,OAAO,SAAS,SAAS,UAAU,OAAO;CACjE,CAAC;CACD,OAAO,UAAU;CAKjB,gBAAgB;EACd,IAAI,CAAC,IAAI,WAAW,IAAI,UAAU,OAChC,IAAI,SAAS,KAAK;CAEtB,GAAG,CAAC,KAAK,KAAK,CAAC;CAEf,MAAM,WAAW,aAAa,UAAkB,IAAI,SAAS,KAAK,GAAG,CAAC,GAAG,CAAC;;;;;;;;;CAU1E,MAAM,SAAS,aACZ,SAAkB;EACjB,MAAM,OAAO,QAAQ,IAAI;EACzB,IAAI,SAAS,IAAI;EACjB,IAAI,OAAO;EACX,QAAQ,IAAI;CACd,GACA,CAAC,KAAK,OAAO,CACf;;CAGA,MAAM,SAAS,kBAAkB;EAC/B,IAAI,SAAS,UAAU,OAAO;EAC9B,IAAI,OAAO;CACb,GAAG,CAAC,GAAG,CAAC;CAKR,MAAM,kBAAkB,cAAyC;EAC/D,IAAI,eAAe,WAAW,eAAe,YAC3C,OAAO,CAAC;EAGV,OAAO;GACL,MAAM;GACN,YAAY,UAAyB;IACnC,IAAI,CAAC,MAAM,qBAAqB,MAAM,QAAQ,WAAW,MAAM,QAAQ,MAAM;KAC3E,MAAM,eAAe;KACrB,IAAI,KAAK;IACX;GACF;EACF;CACF,GAAG,CAAC,YAAY,GAAG,CAAC;CAEpB,MAAM,eAAe,eACZ;EACL,GAAG,IAAI,gBAAgB;EAGvB,cAAc,KAAA;EACd,GAAG;CACL,IACA,CAAC,KAAK,eAAe,CACvB;CAEA,OAAO;EACL;EACA,OAAO,IAAI;EACX,SAAS,IAAI;EACb;EACA;EACA,MAAM,IAAI;EACV;EACA;EACA;EACA;EACA;CACF;AACF;;;;;;;;;ACtNA,IAAM,gBAAgB;AACtB,IAAM,wBAAwB;AAC9B,IAAM,sBAAsB;AAI5B,IAAM,CAAC,4BAA4B,yBAAyB,gBAAyC,aAAa;AAGlH,IAAM,CAAC,wBAAwB,qBAAqB,gBAAsC,aAAa;;AAWvG,IAAM,eAAe,YAEjB,EACE,UACA,YACA,OACA,cACA,eACA,YACA,cACA,UACA,aACA,SACA,iBACA,GAAG,SAEL,iBACG;CACH,MAAM,EAAE,KAAK,iBAAiB,WAAW,YAAY;EACnD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;CAED,OACE,oBAAC,SAAkB,cAAnB;EACE,GAAI;EACJ,OAAO;EACP,WAAW,GAAG,QAAQ,SAAS,GAAG,UAAU;EAC5C,KAAK;EAEL,UAAA,oBAAC,4BAAD;GAA4B,GAAI;GAC9B,UAAA,oBAAC,wBAAD;IAAgC;IAAS;GAAiC,CAAA;EAChD,CAAA;CACE,CAAA;AAEpC,CACF;AAEA,aAAa,cAAc;;AAS3B,IAAM,kBAAkB,YAAmD,EAAE,YAAY,GAAG,SAAS,iBAAiB;CACpH,MAAM,EAAE,WAAW,SAAS,mBAAmB;CAC/C,MAAM,EAAE,MAAM,cAAc,sBAAsB,qBAAqB;CAEvE,OACE,qBAAC,SAAkB,SAAnB;EAGE,cAAY,KAAA;EAEN;EACK;EACX,GAAI;EACJ,WAAW,GAAG,QAAQ,gBAAgB,GAAG,UAAU;EACnD,KAAK;EATP,UAAA,CAWE,oBAAC,QAAD;GAAM,WAAW,QAAQ,oBAAoB;GAAI,UAAA;EAAgB,CAAA,GAGjE,oBAAC,MAAD;GACE,MAAK;GACL,YAAY,QAAQ,oBAAoB;GACxC,eAAY;GAEZ,UAAU,UAAU;IAClB,MAAM,gBAAgB;IACtB,KAAK;GACP;EACD,CAAA,CACwB;;AAE/B,CAAC;AAED,gBAAgB,cAAc;;AAS9B,IAAM,gBAAgB,YAAkD,EAAE,YAAY,GAAG,SAAS,iBAAiB;CACjH,MAAM,EAAE,YAAY,mBAAmB;CACvC,MAAM,EAAE,WAAW,kBAAkB,mBAAmB;CACxD,MAAM,WAAW,OAAgC,IAAI;CAGrD,gBAAgB;EACd,MAAM,UAAU,SAAS;EACzB,IAAI,WAAW,SAAS;GACtB,QAAQ,MAAM;GACd,MAAM,MAAM,QAAQ,MAAM;GAC1B,QAAQ,kBAAkB,KAAK,GAAG;EACpC;CACF,GAAG,CAAC,OAAO,CAAC;CAEZ,OACE,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,SAAkB,OAAnB;EACE,GAAI;EACJ,WAAW,GAAG,QAAQ,cAAc,GAAG,UAAU;EACjD,MAAM,YAAY;GAChB,SAAS,UAAU;GAEnB,MAAM,UAAmB,OAAO,iBAAiB,aAAa,aAAa,OAAO,IAAI,KAAA;GACtF,IAAI,gBAAgB,OAAO,iBAAiB,YAC1C,aAAa,UAAU;GAEzB,aAAa;IACX,SAAS,UAAU;IACnB,IAAI,OAAO,YAAY,YACrB,QAAQ;GAEZ;EACF;CACD,CAAA,GAED,oBAAC,SAAkB,eAAnB;EACE,WAAW,QAAQ,eAAe;EAClC,cAAW;EACX,eAAY;EACZ,eAAe,OAAO;EAEtB,UAAA,oBAAC,MAAD,EAAW,MAAK,kBAAmB,CAAA;CACJ,CAAA,CACjC,EAAA,CAAA;AAEN,CAAC;AAED,cAAc,cAAc"}
|
package/dist/lib/Layout.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-rolldown-runtime.mjs";
|
|
2
2
|
import { n as composableProps, r as slottable } from "./chunk-slots.mjs";
|
|
3
|
-
import { a as useDefaultGutter, i as spanAttributes, n as DefaultGutterProvider, o as useInGrid, r as containerAttributes, t as Container } from "./chunk-Container.mjs";
|
|
4
3
|
import { t as Block } from "./chunk-Block.mjs";
|
|
4
|
+
import { a as useDefaultGutter, i as spanAttributes, n as DefaultGutterProvider, o as useInGrid, r as containerAttributes, t as Container } from "./chunk-Container.mjs";
|
|
5
5
|
import { t as Separator } from "./chunk-Separator.mjs";
|
|
6
6
|
import "react";
|
|
7
7
|
import { mx } from "@dxos/ui-theme";
|
package/dist/lib/Listbox.mjs
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-rolldown-runtime.mjs";
|
|
2
2
|
import { t as recipes } from "./chunk-recipes.mjs";
|
|
3
|
-
import { Icon } from "./Icon.mjs";
|
|
4
3
|
import { n as composableProps, t as composable } from "./chunk-slots.mjs";
|
|
4
|
+
import { Icon } from "./Icon.mjs";
|
|
5
5
|
import { r as containerAttributes } from "./chunk-Container.mjs";
|
|
6
6
|
import { n as ScrollAreaViewport, t as ScrollAreaRoot } from "./chunk-ScrollArea.mjs";
|
|
7
7
|
import { i as useRowTabStops, n as handleGridKeyDown, r as isFromControl, t as RowContext } from "./chunk-grid.mjs";
|
|
8
8
|
import { t as Empty } from "./chunk-Empty.mjs";
|
|
9
9
|
import { Children, createContext, forwardRef, useCallback, useContext, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
10
10
|
import { mx } from "@dxos/ui-theme";
|
|
11
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
12
11
|
import { log } from "@dxos/log";
|
|
12
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
13
13
|
import { useComposedRefs } from "@dxos/react-hooks";
|
|
14
14
|
import { createListCollection } from "@ark-ui/react/collection";
|
|
15
15
|
import { Listbox, useListboxContext } from "@ark-ui/react/listbox";
|
|
@@ -387,7 +387,7 @@ ListboxLabel.displayName = "Listbox.Label";
|
|
|
387
387
|
* The listbox element: zag's content props with the grid keyboard in front of them, a Container's attributes so the
|
|
388
388
|
* ScrollArea viewport slot merges onto it, and the Root's `virtual` windowing over its direct children (the rows).
|
|
389
389
|
*/
|
|
390
|
-
var ListboxViewport = composable(({ gutter, gap, hostScrolls, onKeyDown, onFocus, onBlur, children, ...props }, forwardedRef) => {
|
|
390
|
+
var ListboxViewport = composable(({ gutter, gap, padBlock, hostScrolls, onKeyDown, onFocus, onBlur, children, ...props }, forwardedRef) => {
|
|
391
391
|
const api = useListboxContext();
|
|
392
392
|
const { columns, virtual, scrollToIndexRef } = useRootContext("Content");
|
|
393
393
|
const [element, setElement] = useState(null);
|
|
@@ -402,7 +402,8 @@ var ListboxViewport = composable(({ gutter, gap, hostScrolls, onKeyDown, onFocus
|
|
|
402
402
|
const { style, ...attributes } = containerAttributes({
|
|
403
403
|
gutter,
|
|
404
404
|
gap,
|
|
405
|
-
columns
|
|
405
|
+
columns,
|
|
406
|
+
padBlock
|
|
406
407
|
});
|
|
407
408
|
const { className, style: ownStyle, ...rest } = composableProps(props, { classNames: [recipes.container(), recipes.listboxContent()] });
|
|
408
409
|
const contentProps = api.getContentProps();
|
package/dist/lib/Listbox.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Listbox.mjs","names":[],"sources":["../../src/next/components/Listbox/virtual.tsx","../../src/next/components/Listbox/Listbox.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { type RefCallback, useCallback, useLayoutEffect, useRef, useState } from 'react';\n\nimport { log } from '@dxos/log';\n\n/**\n * How a long list mounts its rows (AUDIT §6 group B). `fixed` windows them: only the rows in view (plus an overscan) are\n * mounted, spacer rows stand in for the rest, and every row must be one height (a dev warning names a list whose rows\n * differ). `measured` windows rows of any height, measuring each as it mounts and estimating the rest from those\n * measured so far. `variable` mounts every row with `content-visibility: auto`, so rows of any height skip layout off\n * screen but every row still renders.\n */\nexport type VirtualMode = 'fixed' | 'measured' | 'variable';\n\n/** Whether a mode mounts only the rows in view, so the caller renders the spans and spacers. */\nexport const isWindowed = (mode: VirtualMode | undefined): mode is 'fixed' | 'measured' =>\n mode === 'fixed' || mode === 'measured';\n\nexport type UseVirtualRowsOptions = {\n mode?: VirtualMode;\n /** The number of rows the list holds. */\n count: number;\n /** Rows mounted beyond each edge of the view. */\n overscan?: number;\n /** A row kept mounted outside the window (e.g. the roving tabstop), so DOM focus is never unmounted. */\n pinned?: number;\n /** Selects the rows `fixed` takes its pitch from (default: the list's non-spacer children); e.g. to skip animating rows. */\n measure?: string;\n /**\n * Selects exactly one element per mounted row, in row order (default: the list's non-spacer children); `measured`\n * observes each one's size and maps it onto the mounted spans to know which row it is.\n */\n rows?: string;\n};\n\n/** A run of mounted rows, `[first, last]` inclusive. */\nexport type VirtualSpan = { first: number; last: number };\n\nexport type VirtualRows = {\n /** Binds the list element; it scrolls itself or is scrolled by its nearest scrolling ancestor. */\n listRef: RefCallback<HTMLElement>;\n /** The mounted range, inclusive; the whole list unless windowed. */\n first: number;\n last: number;\n /** Heights of the spacers before and after the mounted rows. */\n before: number;\n after: number;\n /** The mounted runs in row order: the window, plus the `pinned` row's own run when it is outside it. */\n spans: VirtualSpan[];\n /** The height a spacer takes in place of the unmounted rows `[from, to)` (between spans). */\n spacer: (from: number, to: number) => number;\n /** Scrolls the row at `index` into view, mounting it; for zag's `scrollToIndexFn`. */\n scrollToIndex: (index: number) => void;\n};\n\nconst DEFAULT_OVERSCAN = 8;\n/** A row height to window with before the first row is measured. */\nconst NOMINAL_ROW = 32;\n\nconst SPACER_ATTRIBUTE = 'data-virtual-spacer';\n\nconst scrollerOf = (list: HTMLElement): HTMLElement => {\n for (let element: HTMLElement | null = list; element; element = element.parentElement) {\n const { overflowY } = getComputedStyle(element);\n if (overflowY === 'auto' || overflowY === 'scroll') {\n return element;\n }\n }\n return list.ownerDocument.scrollingElement instanceof HTMLElement ? list.ownerDocument.scrollingElement : list;\n};\n\n/** The list's top within its scroller's content, so a list below other content windows from where it starts. */\nconst offsetIn = (list: HTMLElement, scroller: HTMLElement): number =>\n scroller === list ? 0 : list.getBoundingClientRect().top - scroller.getBoundingClientRect().top + scroller.scrollTop;\n\nconst mountedRows = (list: HTMLElement, selector: string | undefined): HTMLElement[] =>\n selector\n ? Array.from(list.querySelectorAll<HTMLElement>(selector))\n : Array.from(list.children).filter(\n (child): child is HTMLElement => child instanceof HTMLElement && !child.hasAttribute(SPACER_ATTRIBUTE),\n );\n\nconst spansOf = (first: number, last: number, pinned: number | undefined): VirtualSpan[] => {\n if (last < first) {\n return [];\n }\n if (pinned === undefined || (pinned >= first && pinned <= last)) {\n return [{ first, last }];\n }\n return pinned < first\n ? [\n { first: pinned, last: pinned },\n { first, last },\n ]\n : [\n { first, last },\n { first: pinned, last: pinned },\n ];\n};\n\n/**\n * Row heights for `measured`: each mounted row's own, and the mean of those for a row not yet mounted. A `fixed` list\n * has one height for every row, the first mounted row's.\n */\nclass RowHeights {\n #heights = new Map<number, number>();\n #total = 0;\n readonly measured: boolean;\n fixed = NOMINAL_ROW;\n gap = 0;\n\n constructor(measured: boolean) {\n this.measured = measured;\n }\n\n /** Records a row's height; returns whether it changed. */\n set(index: number, height: number): boolean {\n const previous = this.#heights.get(index);\n if (previous !== undefined && Math.abs(previous - height) <= 0.5) {\n return false;\n }\n this.#total += height - (previous ?? 0);\n this.#heights.set(index, height);\n return true;\n }\n\n /** Drops the rows past the end of a list that shrank, so their heights no longer weigh on the estimate. */\n truncate(count: number): void {\n for (const [index, height] of this.#heights) {\n if (index >= count) {\n this.#heights.delete(index);\n this.#total -= height;\n }\n }\n }\n\n get(index: number): number {\n if (!this.measured) {\n return this.fixed;\n }\n return this.#heights.get(index) ?? (this.#heights.size > 0 ? this.#total / this.#heights.size : NOMINAL_ROW);\n }\n\n /** The extent of rows `[from, to)` with the gaps between them. */\n extent(from: number, to: number): number {\n if (to <= from) {\n return 0;\n }\n if (!this.measured) {\n return (to - from) * (this.fixed + this.gap) - this.gap;\n }\n let extent = (to - from - 1) * this.gap;\n for (let index = from; index < to; index++) {\n extent += this.get(index);\n }\n return extent;\n }\n}\n\n/**\n * Windowing shared by the Next lists (Listbox, OrderedList, Tree). With `fixed` or `measured` it tracks the scroll\n * position of the list's scroller and returns the spans of rows to mount and the spacer heights around them; the caller\n * renders `VirtualSpacer`s and the spans. With `variable` or no mode every row is mounted; `variable` lists set\n * `data-virtual='variable'` so the theme defers off-screen rows.\n */\nexport const useVirtualRows = ({\n mode,\n count,\n overscan = DEFAULT_OVERSCAN,\n pinned,\n measure,\n rows: rowsSelector,\n}: UseVirtualRowsOptions): VirtualRows => {\n const windowed = isWindowed(mode);\n const measured = mode === 'measured';\n const listRef = useRef<HTMLElement | null>(null);\n const [list, setList] = useState<HTMLElement | null>(null);\n const heightsRef = useRef<RowHeights | null>(null);\n if (heightsRef.current?.measured !== measured) {\n heightsRef.current = new RowHeights(measured);\n }\n const heights = heightsRef.current;\n const warnedRef = useRef(false);\n const [range, setRange] = useState({ first: 0, last: 2 * overscan });\n const spansRef = useRef<VirtualSpan[]>([]);\n // Found once per list: walking the ancestors' computed styles on every scroll event costs a style read each time.\n const scrollerRef = useRef<HTMLElement | null>(null);\n const resizeRef = useRef<ResizeObserver | null>(null);\n const observedRef = useRef(new Map<Element, { index: number; height?: number }>());\n\n const bind = useCallback<RefCallback<HTMLElement>>((element) => {\n listRef.current = element;\n setList(element);\n }, []);\n\n const update = useCallback(() => {\n const element = listRef.current;\n const scroller = scrollerRef.current;\n if (!element || !scroller) {\n return;\n }\n const start = Math.max(0, scroller.scrollTop - offsetIn(element, scroller));\n const end = start + scroller.clientHeight;\n let first: number;\n let last: number;\n if (measured) {\n // Walked rather than divided, since every row may differ; the first row whose bottom passes the view's top,\n // through the last row whose top is above its bottom.\n first = count;\n last = count - 1;\n let top = 0;\n for (let index = 0; index < count; index++) {\n const bottom = top + heights.get(index);\n if (first === count && bottom > start) {\n first = index;\n }\n if (top >= end) {\n last = index - 1;\n break;\n }\n top = bottom + heights.gap;\n }\n first = Math.max(0, Math.min(first, count - 1) - overscan);\n last = Math.min(count - 1, last + overscan);\n } else {\n const pitch = heights.fixed + heights.gap;\n first = Math.max(0, Math.floor(start / pitch) - overscan);\n last = Math.min(count - 1, Math.ceil(end / pitch) + overscan);\n }\n setRange((range) => (range.first === first && range.last === last ? range : { first, last }));\n }, [count, overscan, measured, heights]);\n\n // `measured` rows report their own heights through a ResizeObserver, which reads them after layout rather than\n // forcing a layout on every render. Created before the effect below, so a list's first rows are observed in the\n // commit that mounts them.\n useLayoutEffect(() => {\n if (!measured || !list) {\n return;\n }\n const observed = observedRef.current;\n const observer = new ResizeObserver((entries) => {\n let changed = false;\n for (const entry of entries) {\n const row = observed.get(entry.target);\n const height = entry.borderBoxSize[0]?.blockSize;\n if (row && height !== undefined) {\n row.height = height;\n changed = heights.set(row.index, height) || changed;\n }\n }\n if (changed) {\n update();\n }\n });\n resizeRef.current = observer;\n return () => {\n observer.disconnect();\n observed.clear();\n resizeRef.current = null;\n };\n }, [measured, list, heights, update]);\n\n // Maps each mounted row element to its index, which reads no layout; a row that moved keeps its element and size, so\n // no resize reports it at its new index and its last height is carried over here.\n useLayoutEffect(() => {\n const observer = resizeRef.current;\n if (!measured || !list || !observer) {\n return;\n }\n heights.truncate(count);\n const rows = mountedRows(list, rowsSelector);\n const indices = spansRef.current.flatMap(({ first, last }) =>\n Array.from({ length: last - first + 1 }, (_, offset) => first + offset),\n );\n // A count that disagrees means the selector is not one element per row, and no height could be attributed.\n if (rows.length !== indices.length) {\n return;\n }\n const observed = observedRef.current;\n const mounted = new Set<Element>(rows);\n for (const element of observed.keys()) {\n if (!mounted.has(element)) {\n observer.unobserve(element);\n observed.delete(element);\n }\n }\n rows.forEach((element, position) => {\n const index = indices[position];\n const row = observed.get(element);\n if (!row) {\n observed.set(element, { index });\n observer.observe(element);\n } else if (row.index !== index) {\n row.index = index;\n if (row.height !== undefined) {\n heights.set(index, row.height);\n }\n }\n });\n });\n\n // A `fixed` list's rows are all the first mounted row's height.\n useLayoutEffect(() => {\n if (!windowed || measured || !list) {\n return;\n }\n const rows = mountedRows(list, measure);\n const height = rows[0]?.getBoundingClientRect().height;\n if (height) {\n const changed = Math.abs(height - heights.fixed) > 0.5;\n heights.fixed = height;\n // The window was computed with the nominal pitch until now.\n if (changed) {\n update();\n }\n if (process.env.NODE_ENV !== 'production' && !warnedRef.current) {\n const other = rows.find((row) => Math.abs(row.getBoundingClientRect().height - height) > 0.5);\n if (other) {\n warnedRef.current = true;\n log.warn(\"virtual='fixed' rows differ in height; use virtual='measured'\", {\n expected: height,\n actual: other.getBoundingClientRect().height,\n });\n }\n }\n }\n });\n\n useLayoutEffect(() => {\n if (!windowed || !list) {\n return;\n }\n const scroller = scrollerOf(list);\n scrollerRef.current = scroller;\n heights.gap = Number.parseFloat(getComputedStyle(list).rowGap) || 0;\n update();\n const observer = new ResizeObserver(update);\n observer.observe(scroller);\n scroller.addEventListener('scroll', update, { passive: true });\n return () => {\n observer.disconnect();\n scroller.removeEventListener('scroll', update);\n scrollerRef.current = null;\n };\n }, [windowed, list, heights, update]);\n\n const scrollToIndex = useCallback(\n (index: number) => {\n const element = listRef.current;\n const scroller = scrollerRef.current;\n if (!windowed || !element || !scroller) {\n return;\n }\n const top = offsetIn(element, scroller) + heights.extent(0, index) + (index > 0 ? heights.gap : 0);\n const height = heights.get(index);\n if (top < scroller.scrollTop) {\n scroller.scrollTop = top;\n } else if (top + height > scroller.scrollTop + scroller.clientHeight) {\n scroller.scrollTop = top + height - scroller.clientHeight;\n }\n update();\n },\n [windowed, heights, update],\n );\n\n if (!windowed) {\n const spans = spansOf(0, count - 1, undefined);\n spansRef.current = spans;\n return {\n listRef: bind,\n first: 0,\n last: count - 1,\n before: 0,\n after: 0,\n spans,\n spacer: () => 0,\n scrollToIndex,\n };\n }\n\n const first = Math.min(range.first, Math.max(0, count - 1));\n const last = Math.min(range.last, count - 1);\n // A spacer takes the place of its rows and the gaps between them; the grid adds the gap beside it.\n const spacer = (from: number, to: number) => heights.extent(from, to);\n const spans = spansOf(first, last, pinned !== undefined && pinned < count ? pinned : undefined);\n spansRef.current = spans;\n return {\n listRef: bind,\n first,\n last,\n before: spacer(0, spans[0]?.first ?? 0),\n after: spacer((spans.at(-1)?.last ?? -1) + 1, count),\n spans,\n spacer,\n scrollToIndex,\n };\n};\n\nexport type VirtualSpacerProps = {\n height: number;\n};\n\n/** Stands in for the unmounted rows of a windowed list; renders nothing when no rows are hidden on its side. */\nexport const VirtualSpacer = ({ height }: VirtualSpacerProps) =>\n height > 0 ? <div role='none' aria-hidden='true' data-virtual-spacer='' style={{ height }} /> : null;\n","//\n// Copyright 2026 DXOS.org\n//\n\n// @import-as-namespace\n\nimport { createListCollection } from '@ark-ui/react/collection';\nimport { Listbox as ListboxPrimitive, type UseListboxContext, useListboxContext } from '@ark-ui/react/listbox';\nimport React, {\n Children,\n type ComponentPropsWithoutRef,\n type ReactNode,\n type RefObject,\n createContext,\n forwardRef,\n useContext,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react';\n\nimport { useComposedRefs } from '@dxos/react-hooks';\nimport { mx } from '@dxos/ui-theme';\nimport { type ThemedClassName } from '@dxos/ui-types';\n\nimport { composable, composableProps } from '../../../util/slots.ts';\nimport { recipes } from '../../recipes.ts';\nimport { type Size } from '../../sizes.ts';\nimport { type ContainerProps, containerAttributes } from '../Container/Container.tsx';\nimport { Empty } from '../Empty/Empty.tsx';\nimport * as Icon from '../Icon/Icon.tsx';\nimport * as ScrollArea from '../ScrollArea/ScrollArea.tsx';\nimport { RowContext, handleGridKeyDown, isFromControl, useRowTabStops } from './grid.ts';\nimport { type VirtualMode, VirtualSpacer, isWindowed, useVirtualRows } from './virtual.tsx';\n\ntype ListboxOption = {\n value: string;\n label: string;\n disabled?: boolean;\n /** Leading icon of the default row. */\n icon?: string;\n /** Second line of the default row. */\n description?: string;\n};\n\n/** `none` keeps zag's focus, keyboard and typeahead with no selection (AUDIT §6 group B: every list runs the machine). */\ntype ListboxSelectionMode = 'single' | 'multiple' | 'none';\n\ntype RootContextValue = {\n items: readonly ListboxOption[];\n selectionMode: ListboxSelectionMode;\n columns?: string;\n virtual?: VirtualMode;\n /** Set by Content when it windows its rows; zag calls it to bring a highlighted row it has not mounted into view. */\n scrollToIndexRef: RefObject<((index: number) => void) | null>;\n};\n\nconst RootContext = createContext<RootContextValue | undefined>(undefined);\n\nconst useRootContext = (part: string) => {\n const context = useContext(RootContext);\n if (!context) {\n throw new Error(`Listbox.${part} must be inside Listbox.Root`);\n }\n return context;\n};\n\n//\n// Root\n//\n\ntype ListboxRootProps = ThemedClassName<Omit<ComponentPropsWithoutRef<'div'>, 'defaultValue' | 'onSelect'>> & {\n items: readonly ListboxOption[];\n /** `single` by default; `multiple` toggles each item; `none` selects nothing but keeps navigation and typeahead. */\n selectionMode?: ListboxSelectionMode;\n value?: string[];\n defaultValue?: string[];\n onValueChange?: (value: string[]) => void;\n /** Clicking the selected item of a single-selection list clears it. */\n deselectable?: boolean;\n disabled?: boolean;\n /** Arrow keys wrap from the last item to the first and back. */\n loopFocus?: boolean;\n /**\n * Custom tracks for every row, declared once: rows become subgrids of these columns and place their children in\n * order. Without it rows lay out by part (a leading cell, the text over its description, trailing parts).\n */\n columns?: string;\n /** Long lists: `fixed` windows equal-height rows, `measured` windows rows of any height, `variable` defers off-screen rows (`content-visibility`). */\n virtual?: VirtualMode;\n size?: Size;\n};\n\n/**\n * Ark listbox: single, multiple or no selection, with keyboard navigation, typeahead and `aria-selected` from zag. A\n * flex column that caps itself at its parent's height, so a long list scrolls in its Content.\n */\nconst ListboxRoot = forwardRef<HTMLDivElement, ListboxRootProps>(\n (\n {\n classNames,\n items,\n selectionMode = 'single',\n value,\n defaultValue,\n onValueChange,\n deselectable,\n disabled,\n loopFocus,\n columns,\n virtual,\n size,\n children,\n ...props\n },\n forwardedRef,\n ) => {\n const collection = useMemo(\n () =>\n createListCollection<ListboxOption>({\n items: [...items],\n itemToValue: (item) => item.value,\n itemToString: (item) => item.label,\n isItemDisabled: (item) => item.disabled ?? false,\n }),\n [items],\n );\n const scrollToIndexRef = useRef<((index: number) => void) | null>(null);\n const context = useMemo<RootContextValue>(\n () => ({ items, selectionMode, columns, virtual, scrollToIndexRef }),\n [items, selectionMode, columns, virtual],\n );\n\n return (\n <RootContext.Provider value={context}>\n <ListboxPrimitive.Root<ListboxOption>\n {...props}\n collection={collection}\n selectionMode={selectionMode}\n value={value}\n defaultValue={defaultValue}\n onValueChange={onValueChange && (({ value }) => onValueChange(value))}\n deselectable={deselectable}\n disabled={disabled}\n loopFocus={loopFocus}\n scrollToIndexFn={isWindowed(virtual) ? ({ index }) => scrollToIndexRef.current?.(index) : undefined}\n data-size={size}\n className={mx(recipes.listbox(), classNames)}\n ref={forwardedRef}\n >\n {children}\n </ListboxPrimitive.Root>\n </RootContext.Provider>\n );\n },\n);\n\nListboxRoot.displayName = 'Listbox.Root';\n\n//\n// Label\n//\n\ntype ListboxLabelProps = ThemedClassName<ListboxPrimitive.LabelProps>;\n\n/** Names the list (`aria-labelledby` on its Content). */\nconst ListboxLabel = forwardRef<HTMLLabelElement, ListboxLabelProps>(({ classNames, ...props }, forwardedRef) => (\n <ListboxPrimitive.Label {...props} className={mx(recipes.label(), classNames)} ref={forwardedRef} />\n));\n\nListboxLabel.displayName = 'Listbox.Label';\n\n//\n// Content\n//\n\ntype ListboxContentProps = ThemedClassName<ComponentPropsWithoutRef<'div'>> &\n Pick<ScrollArea.RootProps, 'mode' | 'width' | 'native'> &\n Pick<ContainerProps, 'gutter' | 'gap'> & {\n /**\n * `false` renders the rows without a ScrollArea of their own, for a host that already scrolls (a ScrollArea\n * composed in `Panel.Body`): the rows then inherit the host's rails (`gutter='inherit'` by default).\n */\n scroll?: boolean;\n };\n\n/**\n * The listbox element: zag's content props with the grid keyboard in front of them, a Container's attributes so the\n * ScrollArea viewport slot merges onto it, and the Root's `virtual` windowing over its direct children (the rows).\n */\nconst ListboxViewport = composable<\n HTMLDivElement,\n ContainerProps & Omit<ComponentPropsWithoutRef<'div'>, 'className'> & { hostScrolls?: boolean }\n>(({ gutter, gap, hostScrolls, onKeyDown, onFocus, onBlur, children, ...props }, forwardedRef) => {\n const api = useListboxContext();\n const { columns, virtual, scrollToIndexRef } = useRootContext('Content');\n const [element, setElement] = useState<HTMLDivElement | null>(null);\n const rows = Children.toArray(children);\n const windowing = useVirtualRows({ mode: virtual, count: rows.length });\n scrollToIndexRef.current = isWindowed(virtual) ? windowing.scrollToIndex : null;\n const ref = useComposedRefs<HTMLDivElement>(forwardedRef, setElement, windowing.listRef);\n useRowTabStops(element);\n\n const { style, ...attributes } = containerAttributes({ gutter, gap, columns });\n const {\n className,\n style: ownStyle,\n ...rest\n } = composableProps(props, {\n classNames: [recipes.container(), recipes.listboxContent()],\n });\n const contentProps = api.getContentProps();\n return (\n <div\n {...contentProps}\n {...rest}\n {...attributes}\n data-scope='listbox'\n data-part='content'\n data-virtual={virtual}\n style={{ ...contentProps.style, ...style, ...ownStyle }}\n className={className}\n onFocus={(event) => {\n contentProps.onFocus?.(event);\n onFocus?.(event);\n }}\n onBlur={(event) => {\n contentProps.onBlur?.(event);\n onBlur?.(event);\n }}\n onKeyDown={(event) => {\n onKeyDown?.(event);\n if (event.defaultPrevented && event.target === event.currentTarget) {\n return;\n }\n if (handleGridKeyDown(event)) {\n return;\n }\n contentProps.onKeyDown?.(event);\n if (hostScrolls && !isWindowed(virtual)) {\n // zag scrolls the highlighted row into view only when the listbox itself overflows; here the host scrolls.\n const content = event.currentTarget;\n requestAnimationFrame(() =>\n content.querySelector('[data-highlighted]')?.scrollIntoView({ block: 'nearest' }),\n );\n }\n }}\n ref={ref}\n >\n {isWindowed(virtual) ? (\n <>\n <VirtualSpacer height={windowing.before} />\n {rows.slice(windowing.first, windowing.last + 1)}\n <VirtualSpacer height={windowing.after} />\n </>\n ) : (\n children\n )}\n </div>\n );\n});\n\n/**\n * The rows, as the viewport of a thin overlay ScrollArea (decision 5) that grows to fill the Root; the listbox element\n * is a stack Container (`inset` gutter by default), so the thumb sits in its end gutter. With Root `virtual='fixed'`\n * only the rows in view mount: give Content the rows as its direct children.\n */\nconst ListboxContent = forwardRef<HTMLDivElement, ListboxContentProps>(\n ({ classNames, mode, width, native, scroll = true, gutter, gap, children, ...props }, forwardedRef) =>\n scroll ? (\n <ScrollArea.Root mode={mode} width={width} native={native} classNames={recipes.listboxScroll()}>\n <ScrollArea.Viewport asChild>\n <ListboxViewport {...props} gutter={gutter ?? 'inset'} gap={gap} classNames={classNames} ref={forwardedRef}>\n {children}\n </ListboxViewport>\n </ScrollArea.Viewport>\n </ScrollArea.Root>\n ) : (\n <ListboxViewport\n {...props}\n hostScrolls\n gutter={gutter ?? 'inherit'}\n gap={gap}\n classNames={classNames}\n ref={forwardedRef}\n >\n {children}\n </ListboxViewport>\n ),\n);\n\nListboxContent.displayName = 'Listbox.Content';\n\n//\n// Empty\n//\n\ntype ListboxEmptyProps = ComponentPropsWithoutRef<typeof Empty>;\n\n/** `Empty`, rendered only while the Root has no items. */\nconst ListboxEmpty = forwardRef<HTMLDivElement, ListboxEmptyProps>((props, forwardedRef) => {\n const { items } = useRootContext('Empty');\n return items.length === 0 ? <Empty {...props} ref={forwardedRef} /> : null;\n});\n\nListboxEmpty.displayName = 'Listbox.Empty';\n\n//\n// Item\n//\n\n// The option an Item renders, so its parts default to the option's icon, label and description.\nconst ItemContext = createContext<ListboxOption | undefined>(undefined);\n\nconst useItem = (part: string) => {\n const item = useContext(ItemContext);\n if (!item) {\n throw new Error(`Listbox.${part} must be inside Listbox.Item`);\n }\n return item;\n};\n\ntype ListboxItemProps = ThemedClassName<Omit<ComponentPropsWithoutRef<'div'>, 'children'>> & {\n item: ListboxOption;\n /** \"You are here\" (`aria-current`), distinct from selection. */\n current?: boolean;\n /** Pointer movement highlights the item, as the keyboard does. */\n highlightOnHover?: boolean;\n /** Replaces the whole row, composed from `ItemIcon`, `ItemText`, `ItemDescription`, `ItemIndicator` and controls. */\n children?: ReactNode;\n};\n\n/**\n * A row (`option`) at least one block tall: without children, the option's icon, label and description; with them,\n * the caller's parts, which the theme places by part (leading cells, text over description, then trailing cells) or,\n * under Root `columns`, in order across the shared tracks. Pointer presses on a row control stay with the control: they\n * neither select the row nor take focus from the control.\n */\nconst ListboxItem = forwardRef<HTMLDivElement, ListboxItemProps>(\n (\n { classNames, item, current, highlightOnHover, children, style, onMouseDown, onClick, onFocus, ...props },\n forwardedRef,\n ) => {\n const api = useListboxContext();\n const { columns, selectionMode } = useRootContext('Item');\n const textId = useId();\n const row = useMemo(() => ({ textId }), [textId]);\n // Under Root `columns` the row is a subgrid of the Content's tracks (theme); otherwise it has its own template.\n const { style: columnsStyle, ...attributes } = containerAttributes(\n columns ? {} : { columns: 'var(--dx-item-columns)' },\n );\n const itemProps = api.getItemProps({ item, highlightOnHover });\n const content = children ?? (\n <>\n {item.icon && <ListboxItemIcon />}\n <ListboxItemText />\n {item.description != null && <ListboxItemDescription />}\n </>\n );\n\n return (\n <ItemContext.Provider value={item}>\n <RowContext.Provider value={row}>\n <div\n {...itemProps}\n {...props}\n {...attributes}\n aria-current={current ? 'true' : undefined}\n data-scope='listbox'\n data-part='item'\n data-selectable={selectionMode === 'none' ? undefined : ''}\n style={{ ...columnsStyle, ...style }}\n className={mx(recipes.container(), recipes.row(), recipes.listboxItem(), classNames)}\n onMouseDown={(event) => {\n onMouseDown?.(event);\n if (!isFromControl(event.currentTarget, event.target)) {\n itemProps.onMouseDown?.(event);\n }\n }}\n onClick={(event) => {\n onClick?.(event);\n if (!isFromControl(event.currentTarget, event.target)) {\n itemProps.onClick?.(event);\n }\n }}\n onFocus={(event) => {\n onFocus?.(event);\n // A row control took focus (a click or the grid keyboard): the row is the list's current row.\n if (event.target !== event.currentTarget && api.highlightedValue !== item.value) {\n api.highlightValue(item.value);\n }\n }}\n ref={forwardedRef}\n >\n {content}\n </div>\n </RowContext.Provider>\n </ItemContext.Provider>\n );\n },\n);\n\nListboxItem.displayName = 'Listbox.Item';\n\n//\n// ItemIcon\n//\n\ntype ListboxItemIconProps = ThemedClassName<ComponentPropsWithoutRef<'div'>> &\n Partial<Pick<Icon.IconProps, 'icon' | 'hue' | 'valence' | 'label'>>;\n\n/** A block-sized leading cell, so the labels of every row start at the same x; `hue` and the rest reach the Icon. */\nconst ListboxItemIcon = forwardRef<HTMLDivElement, ListboxItemIconProps>(\n ({ classNames, icon, hue, valence, label, children, ...props }, forwardedRef) => {\n const item = useItem('ItemIcon');\n const glyph = icon ?? item.icon;\n return (\n <div\n {...props}\n data-scope='listbox'\n data-part='item-icon'\n className={mx(recipes.block(), recipes.listboxItemIcon(), classNames)}\n ref={forwardedRef}\n >\n {children ?? (glyph && <Icon.Icon icon={glyph} hue={hue} valence={valence} label={label} />)}\n </div>\n );\n },\n);\n\nListboxItemIcon.displayName = 'Listbox.ItemIcon';\n\n//\n// ItemText\n//\n\ntype ListboxItemTextProps = ThemedClassName<ComponentPropsWithoutRef<'p'>> & {\n /** `muted` reads as secondary text, as Typography's tone. */\n tone?: 'default' | 'muted';\n};\n\n/**\n * The row's label, truncated to one line; the option's `label` by default, which typeahead matches either way. Its id\n * names the row's controls (a Remove button, a disclosure caret).\n */\nconst ListboxItemText = forwardRef<HTMLParagraphElement, ListboxItemTextProps>(\n ({ classNames, tone, children, ...props }, forwardedRef) => {\n const item = useItem('ItemText');\n const row = useContext(RowContext);\n return (\n <p\n id={row?.textId}\n {...props}\n data-scope='listbox'\n data-part='item-text'\n data-truncate=''\n data-tone={tone === 'muted' ? tone : undefined}\n className={mx(recipes.typography(), recipes.listboxItemText(), classNames)}\n ref={forwardedRef}\n >\n {children ?? item.label}\n </p>\n );\n },\n);\n\nListboxItemText.displayName = 'Listbox.ItemText';\n\n//\n// ItemDescription\n//\n\ntype ListboxItemDescriptionProps = ThemedClassName<ComponentPropsWithoutRef<'p'>>;\n\n/** A second line under the text in `--color-fg-muted`; the option's `description` by default. */\nconst ListboxItemDescription = forwardRef<HTMLParagraphElement, ListboxItemDescriptionProps>(\n ({ classNames, children, ...props }, forwardedRef) => {\n const item = useItem('ItemDescription');\n return (\n <p\n {...props}\n data-scope='listbox'\n data-part='item-description'\n data-truncate=''\n data-tone='muted'\n className={mx(recipes.typography(), recipes.listboxItemDescription(), classNames)}\n ref={forwardedRef}\n >\n {children ?? item.description}\n </p>\n );\n },\n);\n\nListboxItemDescription.displayName = 'Listbox.ItemDescription';\n\n//\n// ItemIndicator\n//\n\ntype ListboxItemIndicatorProps = ThemedClassName<ComponentPropsWithoutRef<'div'>>;\n\n/** Shown while its item is selected: a check by default, in an icon-sized cell. */\nconst ListboxItemIndicator = forwardRef<HTMLDivElement, ListboxItemIndicatorProps>(\n ({ classNames, children, ...props }, forwardedRef) => {\n const api = useListboxContext();\n const item = useItem('ItemIndicator');\n return (\n <div\n {...api.getItemIndicatorProps({ item })}\n {...props}\n className={mx(recipes.listboxItemIndicator(), classNames)}\n ref={forwardedRef}\n >\n {children ?? <Icon.Icon icon='ph--check--regular' />}\n </div>\n );\n },\n);\n\nListboxItemIndicator.displayName = 'Listbox.ItemIndicator';\n\n//\n// ItemGroup\n//\n\ntype ListboxItemGroupProps = ThemedClassName<ComponentPropsWithoutRef<'div'>>;\n\n/**\n * A `group` of items named by the `ItemGroupLabel` inside it; an inheriting Container, so its rows keep the list's\n * rails. Order groups as the Root's `items` are ordered, or keyboard order and reading order diverge.\n */\nconst ListboxItemGroup = forwardRef<HTMLDivElement, ListboxItemGroupProps>(\n ({ classNames, style, children, ...props }, forwardedRef) => {\n const { style: containerStyle, ...attributes } = containerAttributes({});\n return (\n <ListboxPrimitive.ItemGroup\n {...props}\n {...attributes}\n style={{ ...containerStyle, ...style }}\n className={mx(recipes.container(), classNames)}\n data-scope='listbox'\n data-part='item-group'\n ref={forwardedRef}\n >\n {children}\n </ListboxPrimitive.ItemGroup>\n );\n },\n);\n\nListboxItemGroup.displayName = 'Listbox.ItemGroup';\n\n//\n// ItemGroupLabel\n//\n\ntype ListboxItemGroupLabelProps = ThemedClassName<ComponentPropsWithoutRef<'div'>>;\n\n/** A small caption naming its group, like `Select.ItemGroupLabel`. */\nconst ListboxItemGroupLabel = forwardRef<HTMLDivElement, ListboxItemGroupLabelProps>(\n ({ classNames, ...props }, forwardedRef) => (\n <ListboxPrimitive.ItemGroupLabel\n {...props}\n data-scope='listbox'\n data-part='item-group-label'\n className={mx(recipes.popupGroupLabel(), classNames)}\n ref={forwardedRef}\n />\n ),\n);\n\nListboxItemGroupLabel.displayName = 'Listbox.ItemGroupLabel';\n\n//\n// useContext\n//\n\ntype ListboxContext = UseListboxContext<ListboxOption>;\n\n/** Ark's listbox api (`value`, `selectedItems`, `setValue`, `clearValue`, …) for parts inside the Root, e.g. a detail pane. */\nconst useListboxRootContext = (): ListboxContext => useListboxContext();\nexport type {\n ListboxContentProps as ContentProps,\n ListboxContext as Context,\n ListboxEmptyProps as EmptyProps,\n ListboxItemDescriptionProps as ItemDescriptionProps,\n ListboxItemGroupLabelProps as ItemGroupLabelProps,\n ListboxItemGroupProps as ItemGroupProps,\n ListboxItemIconProps as ItemIconProps,\n ListboxItemIndicatorProps as ItemIndicatorProps,\n ListboxItemProps as ItemProps,\n ListboxItemTextProps as ItemTextProps,\n ListboxLabelProps as LabelProps,\n ListboxRootProps as RootProps,\n};\n\nexport {\n ListboxContent as Content,\n ListboxEmpty as Empty,\n ListboxItem as Item,\n ListboxItemDescription as ItemDescription,\n ListboxItemGroup as ItemGroup,\n ListboxItemGroupLabel as ItemGroupLabel,\n ListboxItemIcon as ItemIcon,\n ListboxItemIndicator as ItemIndicator,\n ListboxItemText as ItemText,\n ListboxLabel as Label,\n ListboxRoot as Root,\n useListboxRootContext as useContext,\n};\nexport type { ListboxOption as Option, ListboxSelectionMode as SelectionMode };\nexport { RowContext, type RowContextValue } from './grid.ts';\nexport * from './virtual.tsx';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Listbox.mjs","names":[],"sources":["../../src/next/components/Listbox/virtual.tsx","../../src/next/components/Listbox/Listbox.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { type RefCallback, useCallback, useLayoutEffect, useRef, useState } from 'react';\n\nimport { log } from '@dxos/log';\n\n/**\n * How a long list mounts its rows (AUDIT §6 group B). `fixed` windows them: only the rows in view (plus an overscan) are\n * mounted, spacer rows stand in for the rest, and every row must be one height (a dev warning names a list whose rows\n * differ). `measured` windows rows of any height, measuring each as it mounts and estimating the rest from those\n * measured so far. `variable` mounts every row with `content-visibility: auto`, so rows of any height skip layout off\n * screen but every row still renders.\n */\nexport type VirtualMode = 'fixed' | 'measured' | 'variable';\n\n/** Whether a mode mounts only the rows in view, so the caller renders the spans and spacers. */\nexport const isWindowed = (mode: VirtualMode | undefined): mode is 'fixed' | 'measured' =>\n mode === 'fixed' || mode === 'measured';\n\nexport type UseVirtualRowsOptions = {\n mode?: VirtualMode;\n /** The number of rows the list holds. */\n count: number;\n /** Rows mounted beyond each edge of the view. */\n overscan?: number;\n /** A row kept mounted outside the window (e.g. the roving tabstop), so DOM focus is never unmounted. */\n pinned?: number;\n /** Selects the rows `fixed` takes its pitch from (default: the list's non-spacer children); e.g. to skip animating rows. */\n measure?: string;\n /**\n * Selects exactly one element per mounted row, in row order (default: the list's non-spacer children); `measured`\n * observes each one's size and maps it onto the mounted spans to know which row it is.\n */\n rows?: string;\n};\n\n/** A run of mounted rows, `[first, last]` inclusive. */\nexport type VirtualSpan = { first: number; last: number };\n\nexport type VirtualRows = {\n /** Binds the list element; it scrolls itself or is scrolled by its nearest scrolling ancestor. */\n listRef: RefCallback<HTMLElement>;\n /** The mounted range, inclusive; the whole list unless windowed. */\n first: number;\n last: number;\n /** Heights of the spacers before and after the mounted rows. */\n before: number;\n after: number;\n /** The mounted runs in row order: the window, plus the `pinned` row's own run when it is outside it. */\n spans: VirtualSpan[];\n /** The height a spacer takes in place of the unmounted rows `[from, to)` (between spans). */\n spacer: (from: number, to: number) => number;\n /** Scrolls the row at `index` into view, mounting it; for zag's `scrollToIndexFn`. */\n scrollToIndex: (index: number) => void;\n};\n\nconst DEFAULT_OVERSCAN = 8;\n/** A row height to window with before the first row is measured. */\nconst NOMINAL_ROW = 32;\n\nconst SPACER_ATTRIBUTE = 'data-virtual-spacer';\n\nconst scrollerOf = (list: HTMLElement): HTMLElement => {\n for (let element: HTMLElement | null = list; element; element = element.parentElement) {\n const { overflowY } = getComputedStyle(element);\n if (overflowY === 'auto' || overflowY === 'scroll') {\n return element;\n }\n }\n return list.ownerDocument.scrollingElement instanceof HTMLElement ? list.ownerDocument.scrollingElement : list;\n};\n\n/** The list's top within its scroller's content, so a list below other content windows from where it starts. */\nconst offsetIn = (list: HTMLElement, scroller: HTMLElement): number =>\n scroller === list ? 0 : list.getBoundingClientRect().top - scroller.getBoundingClientRect().top + scroller.scrollTop;\n\nconst mountedRows = (list: HTMLElement, selector: string | undefined): HTMLElement[] =>\n selector\n ? Array.from(list.querySelectorAll<HTMLElement>(selector))\n : Array.from(list.children).filter(\n (child): child is HTMLElement => child instanceof HTMLElement && !child.hasAttribute(SPACER_ATTRIBUTE),\n );\n\nconst spansOf = (first: number, last: number, pinned: number | undefined): VirtualSpan[] => {\n if (last < first) {\n return [];\n }\n if (pinned === undefined || (pinned >= first && pinned <= last)) {\n return [{ first, last }];\n }\n return pinned < first\n ? [\n { first: pinned, last: pinned },\n { first, last },\n ]\n : [\n { first, last },\n { first: pinned, last: pinned },\n ];\n};\n\n/**\n * Row heights for `measured`: each mounted row's own, and the mean of those for a row not yet mounted. A `fixed` list\n * has one height for every row, the first mounted row's.\n */\nclass RowHeights {\n #heights = new Map<number, number>();\n #total = 0;\n readonly measured: boolean;\n fixed = NOMINAL_ROW;\n gap = 0;\n\n constructor(measured: boolean) {\n this.measured = measured;\n }\n\n /** Records a row's height; returns whether it changed. */\n set(index: number, height: number): boolean {\n const previous = this.#heights.get(index);\n if (previous !== undefined && Math.abs(previous - height) <= 0.5) {\n return false;\n }\n this.#total += height - (previous ?? 0);\n this.#heights.set(index, height);\n return true;\n }\n\n /** Drops the rows past the end of a list that shrank, so their heights no longer weigh on the estimate. */\n truncate(count: number): void {\n for (const [index, height] of this.#heights) {\n if (index >= count) {\n this.#heights.delete(index);\n this.#total -= height;\n }\n }\n }\n\n get(index: number): number {\n if (!this.measured) {\n return this.fixed;\n }\n return this.#heights.get(index) ?? (this.#heights.size > 0 ? this.#total / this.#heights.size : NOMINAL_ROW);\n }\n\n /** The extent of rows `[from, to)` with the gaps between them. */\n extent(from: number, to: number): number {\n if (to <= from) {\n return 0;\n }\n if (!this.measured) {\n return (to - from) * (this.fixed + this.gap) - this.gap;\n }\n let extent = (to - from - 1) * this.gap;\n for (let index = from; index < to; index++) {\n extent += this.get(index);\n }\n return extent;\n }\n}\n\n/**\n * Windowing shared by the Next lists (Listbox, OrderedList, Tree). With `fixed` or `measured` it tracks the scroll\n * position of the list's scroller and returns the spans of rows to mount and the spacer heights around them; the caller\n * renders `VirtualSpacer`s and the spans. With `variable` or no mode every row is mounted; `variable` lists set\n * `data-virtual='variable'` so the theme defers off-screen rows.\n */\nexport const useVirtualRows = ({\n mode,\n count,\n overscan = DEFAULT_OVERSCAN,\n pinned,\n measure,\n rows: rowsSelector,\n}: UseVirtualRowsOptions): VirtualRows => {\n const windowed = isWindowed(mode);\n const measured = mode === 'measured';\n const listRef = useRef<HTMLElement | null>(null);\n const [list, setList] = useState<HTMLElement | null>(null);\n const heightsRef = useRef<RowHeights | null>(null);\n if (heightsRef.current?.measured !== measured) {\n heightsRef.current = new RowHeights(measured);\n }\n const heights = heightsRef.current;\n const warnedRef = useRef(false);\n const [range, setRange] = useState({ first: 0, last: 2 * overscan });\n const spansRef = useRef<VirtualSpan[]>([]);\n // Found once per list: walking the ancestors' computed styles on every scroll event costs a style read each time.\n const scrollerRef = useRef<HTMLElement | null>(null);\n const resizeRef = useRef<ResizeObserver | null>(null);\n const observedRef = useRef(new Map<Element, { index: number; height?: number }>());\n\n const bind = useCallback<RefCallback<HTMLElement>>((element) => {\n listRef.current = element;\n setList(element);\n }, []);\n\n const update = useCallback(() => {\n const element = listRef.current;\n const scroller = scrollerRef.current;\n if (!element || !scroller) {\n return;\n }\n const start = Math.max(0, scroller.scrollTop - offsetIn(element, scroller));\n const end = start + scroller.clientHeight;\n let first: number;\n let last: number;\n if (measured) {\n // Walked rather than divided, since every row may differ; the first row whose bottom passes the view's top,\n // through the last row whose top is above its bottom.\n first = count;\n last = count - 1;\n let top = 0;\n for (let index = 0; index < count; index++) {\n const bottom = top + heights.get(index);\n if (first === count && bottom > start) {\n first = index;\n }\n if (top >= end) {\n last = index - 1;\n break;\n }\n top = bottom + heights.gap;\n }\n first = Math.max(0, Math.min(first, count - 1) - overscan);\n last = Math.min(count - 1, last + overscan);\n } else {\n const pitch = heights.fixed + heights.gap;\n first = Math.max(0, Math.floor(start / pitch) - overscan);\n last = Math.min(count - 1, Math.ceil(end / pitch) + overscan);\n }\n setRange((range) => (range.first === first && range.last === last ? range : { first, last }));\n }, [count, overscan, measured, heights]);\n\n // `measured` rows report their own heights through a ResizeObserver, which reads them after layout rather than\n // forcing a layout on every render. Created before the effect below, so a list's first rows are observed in the\n // commit that mounts them.\n useLayoutEffect(() => {\n if (!measured || !list) {\n return;\n }\n const observed = observedRef.current;\n const observer = new ResizeObserver((entries) => {\n let changed = false;\n for (const entry of entries) {\n const row = observed.get(entry.target);\n const height = entry.borderBoxSize[0]?.blockSize;\n if (row && height !== undefined) {\n row.height = height;\n changed = heights.set(row.index, height) || changed;\n }\n }\n if (changed) {\n update();\n }\n });\n resizeRef.current = observer;\n return () => {\n observer.disconnect();\n observed.clear();\n resizeRef.current = null;\n };\n }, [measured, list, heights, update]);\n\n // Maps each mounted row element to its index, which reads no layout; a row that moved keeps its element and size, so\n // no resize reports it at its new index and its last height is carried over here.\n useLayoutEffect(() => {\n const observer = resizeRef.current;\n if (!measured || !list || !observer) {\n return;\n }\n heights.truncate(count);\n const rows = mountedRows(list, rowsSelector);\n const indices = spansRef.current.flatMap(({ first, last }) =>\n Array.from({ length: last - first + 1 }, (_, offset) => first + offset),\n );\n // A count that disagrees means the selector is not one element per row, and no height could be attributed.\n if (rows.length !== indices.length) {\n return;\n }\n const observed = observedRef.current;\n const mounted = new Set<Element>(rows);\n for (const element of observed.keys()) {\n if (!mounted.has(element)) {\n observer.unobserve(element);\n observed.delete(element);\n }\n }\n rows.forEach((element, position) => {\n const index = indices[position];\n const row = observed.get(element);\n if (!row) {\n observed.set(element, { index });\n observer.observe(element);\n } else if (row.index !== index) {\n row.index = index;\n if (row.height !== undefined) {\n heights.set(index, row.height);\n }\n }\n });\n });\n\n // A `fixed` list's rows are all the first mounted row's height.\n useLayoutEffect(() => {\n if (!windowed || measured || !list) {\n return;\n }\n const rows = mountedRows(list, measure);\n const height = rows[0]?.getBoundingClientRect().height;\n if (height) {\n const changed = Math.abs(height - heights.fixed) > 0.5;\n heights.fixed = height;\n // The window was computed with the nominal pitch until now.\n if (changed) {\n update();\n }\n if (process.env.NODE_ENV !== 'production' && !warnedRef.current) {\n const other = rows.find((row) => Math.abs(row.getBoundingClientRect().height - height) > 0.5);\n if (other) {\n warnedRef.current = true;\n log.warn(\"virtual='fixed' rows differ in height; use virtual='measured'\", {\n expected: height,\n actual: other.getBoundingClientRect().height,\n });\n }\n }\n }\n });\n\n useLayoutEffect(() => {\n if (!windowed || !list) {\n return;\n }\n const scroller = scrollerOf(list);\n scrollerRef.current = scroller;\n heights.gap = Number.parseFloat(getComputedStyle(list).rowGap) || 0;\n update();\n const observer = new ResizeObserver(update);\n observer.observe(scroller);\n scroller.addEventListener('scroll', update, { passive: true });\n return () => {\n observer.disconnect();\n scroller.removeEventListener('scroll', update);\n scrollerRef.current = null;\n };\n }, [windowed, list, heights, update]);\n\n const scrollToIndex = useCallback(\n (index: number) => {\n const element = listRef.current;\n const scroller = scrollerRef.current;\n if (!windowed || !element || !scroller) {\n return;\n }\n const top = offsetIn(element, scroller) + heights.extent(0, index) + (index > 0 ? heights.gap : 0);\n const height = heights.get(index);\n if (top < scroller.scrollTop) {\n scroller.scrollTop = top;\n } else if (top + height > scroller.scrollTop + scroller.clientHeight) {\n scroller.scrollTop = top + height - scroller.clientHeight;\n }\n update();\n },\n [windowed, heights, update],\n );\n\n if (!windowed) {\n const spans = spansOf(0, count - 1, undefined);\n spansRef.current = spans;\n return {\n listRef: bind,\n first: 0,\n last: count - 1,\n before: 0,\n after: 0,\n spans,\n spacer: () => 0,\n scrollToIndex,\n };\n }\n\n const first = Math.min(range.first, Math.max(0, count - 1));\n const last = Math.min(range.last, count - 1);\n // A spacer takes the place of its rows and the gaps between them; the grid adds the gap beside it.\n const spacer = (from: number, to: number) => heights.extent(from, to);\n const spans = spansOf(first, last, pinned !== undefined && pinned < count ? pinned : undefined);\n spansRef.current = spans;\n return {\n listRef: bind,\n first,\n last,\n before: spacer(0, spans[0]?.first ?? 0),\n after: spacer((spans.at(-1)?.last ?? -1) + 1, count),\n spans,\n spacer,\n scrollToIndex,\n };\n};\n\nexport type VirtualSpacerProps = {\n height: number;\n};\n\n/** Stands in for the unmounted rows of a windowed list; renders nothing when no rows are hidden on its side. */\nexport const VirtualSpacer = ({ height }: VirtualSpacerProps) =>\n height > 0 ? <div role='none' aria-hidden='true' data-virtual-spacer='' style={{ height }} /> : null;\n","//\n// Copyright 2026 DXOS.org\n//\n\n// @import-as-namespace\n\nimport { createListCollection } from '@ark-ui/react/collection';\nimport { Listbox as ListboxPrimitive, type UseListboxContext, useListboxContext } from '@ark-ui/react/listbox';\nimport React, {\n Children,\n type ComponentPropsWithoutRef,\n type ReactNode,\n type RefObject,\n createContext,\n forwardRef,\n useContext,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react';\n\nimport { useComposedRefs } from '@dxos/react-hooks';\nimport { mx } from '@dxos/ui-theme';\nimport { type ThemedClassName } from '@dxos/ui-types';\n\nimport { composable, composableProps } from '../../../util/slots.ts';\nimport { recipes } from '../../recipes.ts';\nimport { type Size } from '../../sizes.ts';\nimport { type ContainerProps, containerAttributes } from '../Container/Container.tsx';\nimport { Empty } from '../Empty/Empty.tsx';\nimport * as Icon from '../Icon/Icon.tsx';\nimport * as ScrollArea from '../ScrollArea/ScrollArea.tsx';\nimport { RowContext, handleGridKeyDown, isFromControl, useRowTabStops } from './grid.ts';\nimport { type VirtualMode, VirtualSpacer, isWindowed, useVirtualRows } from './virtual.tsx';\n\ntype ListboxOption = {\n value: string;\n label: string;\n disabled?: boolean;\n /** Leading icon of the default row. */\n icon?: string;\n /** Second line of the default row. */\n description?: string;\n};\n\n/** `none` keeps zag's focus, keyboard and typeahead with no selection (AUDIT §6 group B: every list runs the machine). */\ntype ListboxSelectionMode = 'single' | 'multiple' | 'extended' | 'none';\n\ntype RootContextValue = {\n items: readonly ListboxOption[];\n selectionMode: ListboxSelectionMode;\n columns?: string;\n virtual?: VirtualMode;\n /** Set by Content when it windows its rows; zag calls it to bring a highlighted row it has not mounted into view. */\n scrollToIndexRef: RefObject<((index: number) => void) | null>;\n};\n\nconst RootContext = createContext<RootContextValue | undefined>(undefined);\n\nconst useRootContext = (part: string) => {\n const context = useContext(RootContext);\n if (!context) {\n throw new Error(`Listbox.${part} must be inside Listbox.Root`);\n }\n return context;\n};\n\n//\n// Root\n//\n\ntype ListboxRootProps = ThemedClassName<Omit<ComponentPropsWithoutRef<'div'>, 'defaultValue' | 'onSelect'>> & {\n items: readonly ListboxOption[];\n /**\n * `single` by default; `multiple` toggles each item; `extended` selects one on a click and adds with Cmd/Ctrl or a\n * Shift range, as a desktop list does; `none` selects nothing but keeps navigation and typeahead.\n */\n selectionMode?: ListboxSelectionMode;\n value?: string[];\n defaultValue?: string[];\n onValueChange?: (value: string[]) => void;\n /** Clicking the selected item of a single-selection list clears it. */\n deselectable?: boolean;\n disabled?: boolean;\n /** Arrow keys wrap from the last item to the first and back. */\n loopFocus?: boolean;\n /**\n * Custom tracks for every row, declared once: rows become subgrids of these columns and place their children in\n * order. Without it rows lay out by part (a leading cell, the text over its description, trailing parts).\n */\n columns?: string;\n /** Long lists: `fixed` windows equal-height rows, `measured` windows rows of any height, `variable` defers off-screen rows (`content-visibility`). */\n virtual?: VirtualMode;\n size?: Size;\n};\n\n/**\n * Ark listbox: single, multiple or no selection, with keyboard navigation, typeahead and `aria-selected` from zag. A\n * flex column that caps itself at its parent's height, so a long list scrolls in its Content.\n */\nconst ListboxRoot = forwardRef<HTMLDivElement, ListboxRootProps>(\n (\n {\n classNames,\n items,\n selectionMode = 'single',\n value,\n defaultValue,\n onValueChange,\n deselectable,\n disabled,\n loopFocus,\n columns,\n virtual,\n size,\n children,\n ...props\n },\n forwardedRef,\n ) => {\n const collection = useMemo(\n () =>\n createListCollection<ListboxOption>({\n items: [...items],\n itemToValue: (item) => item.value,\n itemToString: (item) => item.label,\n isItemDisabled: (item) => item.disabled ?? false,\n }),\n [items],\n );\n const scrollToIndexRef = useRef<((index: number) => void) | null>(null);\n const context = useMemo<RootContextValue>(\n () => ({ items, selectionMode, columns, virtual, scrollToIndexRef }),\n [items, selectionMode, columns, virtual],\n );\n\n return (\n <RootContext.Provider value={context}>\n <ListboxPrimitive.Root<ListboxOption>\n {...props}\n collection={collection}\n selectionMode={selectionMode}\n value={value}\n defaultValue={defaultValue}\n onValueChange={onValueChange && (({ value }) => onValueChange(value))}\n deselectable={deselectable}\n disabled={disabled}\n loopFocus={loopFocus}\n scrollToIndexFn={isWindowed(virtual) ? ({ index }) => scrollToIndexRef.current?.(index) : undefined}\n data-size={size}\n className={mx(recipes.listbox(), classNames)}\n ref={forwardedRef}\n >\n {children}\n </ListboxPrimitive.Root>\n </RootContext.Provider>\n );\n },\n);\n\nListboxRoot.displayName = 'Listbox.Root';\n\n//\n// Label\n//\n\ntype ListboxLabelProps = ThemedClassName<ListboxPrimitive.LabelProps>;\n\n/** Names the list (`aria-labelledby` on its Content). */\nconst ListboxLabel = forwardRef<HTMLLabelElement, ListboxLabelProps>(({ classNames, ...props }, forwardedRef) => (\n <ListboxPrimitive.Label {...props} className={mx(recipes.label(), classNames)} ref={forwardedRef} />\n));\n\nListboxLabel.displayName = 'Listbox.Label';\n\n//\n// Content\n//\n\ntype ListboxContentProps = ThemedClassName<ComponentPropsWithoutRef<'div'>> &\n Pick<ScrollArea.RootProps, 'mode' | 'width' | 'native'> &\n Pick<ContainerProps, 'gutter' | 'gap' | 'padBlock'> & {\n /**\n * `false` renders the rows without a ScrollArea of their own, for a host that already scrolls (a ScrollArea\n * composed in `Panel.Body`): the rows then inherit the host's rails (`gutter='inherit'` by default).\n */\n scroll?: boolean;\n };\n\n/**\n * The listbox element: zag's content props with the grid keyboard in front of them, a Container's attributes so the\n * ScrollArea viewport slot merges onto it, and the Root's `virtual` windowing over its direct children (the rows).\n */\nconst ListboxViewport = composable<\n HTMLDivElement,\n ContainerProps & Omit<ComponentPropsWithoutRef<'div'>, 'className'> & { hostScrolls?: boolean }\n>(({ gutter, gap, padBlock, hostScrolls, onKeyDown, onFocus, onBlur, children, ...props }, forwardedRef) => {\n const api = useListboxContext();\n const { columns, virtual, scrollToIndexRef } = useRootContext('Content');\n const [element, setElement] = useState<HTMLDivElement | null>(null);\n const rows = Children.toArray(children);\n const windowing = useVirtualRows({ mode: virtual, count: rows.length });\n scrollToIndexRef.current = isWindowed(virtual) ? windowing.scrollToIndex : null;\n const ref = useComposedRefs<HTMLDivElement>(forwardedRef, setElement, windowing.listRef);\n useRowTabStops(element);\n\n const { style, ...attributes } = containerAttributes({ gutter, gap, columns, padBlock });\n const {\n className,\n style: ownStyle,\n ...rest\n } = composableProps(props, {\n classNames: [recipes.container(), recipes.listboxContent()],\n });\n const contentProps = api.getContentProps();\n return (\n <div\n {...contentProps}\n {...rest}\n {...attributes}\n data-scope='listbox'\n data-part='content'\n data-virtual={virtual}\n style={{ ...contentProps.style, ...style, ...ownStyle }}\n className={className}\n onFocus={(event) => {\n contentProps.onFocus?.(event);\n onFocus?.(event);\n }}\n onBlur={(event) => {\n contentProps.onBlur?.(event);\n onBlur?.(event);\n }}\n onKeyDown={(event) => {\n onKeyDown?.(event);\n if (event.defaultPrevented && event.target === event.currentTarget) {\n return;\n }\n if (handleGridKeyDown(event)) {\n return;\n }\n contentProps.onKeyDown?.(event);\n if (hostScrolls && !isWindowed(virtual)) {\n // zag scrolls the highlighted row into view only when the listbox itself overflows; here the host scrolls.\n const content = event.currentTarget;\n requestAnimationFrame(() =>\n content.querySelector('[data-highlighted]')?.scrollIntoView({ block: 'nearest' }),\n );\n }\n }}\n ref={ref}\n >\n {isWindowed(virtual) ? (\n <>\n <VirtualSpacer height={windowing.before} />\n {rows.slice(windowing.first, windowing.last + 1)}\n <VirtualSpacer height={windowing.after} />\n </>\n ) : (\n children\n )}\n </div>\n );\n});\n\n/**\n * The rows, as the viewport of a thin overlay ScrollArea (decision 5) that grows to fill the Root; the listbox element\n * is a stack Container (`inset` gutter by default), so the thumb sits in its end gutter. With Root `virtual='fixed'`\n * only the rows in view mount: give Content the rows as its direct children.\n */\nconst ListboxContent = forwardRef<HTMLDivElement, ListboxContentProps>(\n ({ classNames, mode, width, native, scroll = true, gutter, gap, children, ...props }, forwardedRef) =>\n scroll ? (\n <ScrollArea.Root mode={mode} width={width} native={native} classNames={recipes.listboxScroll()}>\n <ScrollArea.Viewport asChild>\n <ListboxViewport {...props} gutter={gutter ?? 'inset'} gap={gap} classNames={classNames} ref={forwardedRef}>\n {children}\n </ListboxViewport>\n </ScrollArea.Viewport>\n </ScrollArea.Root>\n ) : (\n <ListboxViewport\n {...props}\n hostScrolls\n gutter={gutter ?? 'inherit'}\n gap={gap}\n classNames={classNames}\n ref={forwardedRef}\n >\n {children}\n </ListboxViewport>\n ),\n);\n\nListboxContent.displayName = 'Listbox.Content';\n\n//\n// Empty\n//\n\ntype ListboxEmptyProps = ComponentPropsWithoutRef<typeof Empty>;\n\n/** `Empty`, rendered only while the Root has no items. */\nconst ListboxEmpty = forwardRef<HTMLDivElement, ListboxEmptyProps>((props, forwardedRef) => {\n const { items } = useRootContext('Empty');\n return items.length === 0 ? <Empty {...props} ref={forwardedRef} /> : null;\n});\n\nListboxEmpty.displayName = 'Listbox.Empty';\n\n//\n// Item\n//\n\n// The option an Item renders, so its parts default to the option's icon, label and description.\nconst ItemContext = createContext<ListboxOption | undefined>(undefined);\n\nconst useItem = (part: string) => {\n const item = useContext(ItemContext);\n if (!item) {\n throw new Error(`Listbox.${part} must be inside Listbox.Item`);\n }\n return item;\n};\n\ntype ListboxItemProps = ThemedClassName<Omit<ComponentPropsWithoutRef<'div'>, 'children'>> & {\n item: ListboxOption;\n /** \"You are here\" (`aria-current`), distinct from selection. */\n current?: boolean;\n /** Pointer movement highlights the item, as the keyboard does. */\n highlightOnHover?: boolean;\n /** Replaces the whole row, composed from `ItemIcon`, `ItemText`, `ItemDescription`, `ItemIndicator` and controls. */\n children?: ReactNode;\n};\n\n/**\n * A row (`option`) at least one block tall: without children, the option's icon, label and description; with them,\n * the caller's parts, which the theme places by part (leading cells, text over description, then trailing cells) or,\n * under Root `columns`, in order across the shared tracks. Pointer presses on a row control stay with the control: they\n * neither select the row nor take focus from the control.\n */\nconst ListboxItem = forwardRef<HTMLDivElement, ListboxItemProps>(\n (\n { classNames, item, current, highlightOnHover, children, style, onMouseDown, onClick, onFocus, ...props },\n forwardedRef,\n ) => {\n const api = useListboxContext();\n const { columns, selectionMode } = useRootContext('Item');\n const textId = useId();\n const row = useMemo(() => ({ textId }), [textId]);\n // Under Root `columns` the row is a subgrid of the Content's tracks (theme); otherwise it has its own template.\n const { style: columnsStyle, ...attributes } = containerAttributes(\n columns ? {} : { columns: 'var(--dx-item-columns)' },\n );\n const itemProps = api.getItemProps({ item, highlightOnHover });\n const content = children ?? (\n <>\n {item.icon && <ListboxItemIcon />}\n <ListboxItemText />\n {item.description != null && <ListboxItemDescription />}\n </>\n );\n\n return (\n <ItemContext.Provider value={item}>\n <RowContext.Provider value={row}>\n <div\n {...itemProps}\n {...props}\n {...attributes}\n aria-current={current ? 'true' : undefined}\n data-scope='listbox'\n data-part='item'\n data-selectable={selectionMode === 'none' ? undefined : ''}\n style={{ ...columnsStyle, ...style }}\n className={mx(recipes.container(), recipes.row(), recipes.listboxItem(), classNames)}\n onMouseDown={(event) => {\n onMouseDown?.(event);\n if (!isFromControl(event.currentTarget, event.target)) {\n itemProps.onMouseDown?.(event);\n }\n }}\n onClick={(event) => {\n onClick?.(event);\n if (!isFromControl(event.currentTarget, event.target)) {\n itemProps.onClick?.(event);\n }\n }}\n onFocus={(event) => {\n onFocus?.(event);\n // A row control took focus (a click or the grid keyboard): the row is the list's current row.\n if (event.target !== event.currentTarget && api.highlightedValue !== item.value) {\n api.highlightValue(item.value);\n }\n }}\n ref={forwardedRef}\n >\n {content}\n </div>\n </RowContext.Provider>\n </ItemContext.Provider>\n );\n },\n);\n\nListboxItem.displayName = 'Listbox.Item';\n\n//\n// ItemIcon\n//\n\ntype ListboxItemIconProps = ThemedClassName<ComponentPropsWithoutRef<'div'>> &\n Partial<Pick<Icon.IconProps, 'icon' | 'hue' | 'valence' | 'label'>>;\n\n/** A block-sized leading cell, so the labels of every row start at the same x; `hue` and the rest reach the Icon. */\nconst ListboxItemIcon = forwardRef<HTMLDivElement, ListboxItemIconProps>(\n ({ classNames, icon, hue, valence, label, children, ...props }, forwardedRef) => {\n const item = useItem('ItemIcon');\n const glyph = icon ?? item.icon;\n return (\n <div\n {...props}\n data-scope='listbox'\n data-part='item-icon'\n className={mx(recipes.block(), recipes.listboxItemIcon(), classNames)}\n ref={forwardedRef}\n >\n {children ?? (glyph && <Icon.Icon icon={glyph} hue={hue} valence={valence} label={label} />)}\n </div>\n );\n },\n);\n\nListboxItemIcon.displayName = 'Listbox.ItemIcon';\n\n//\n// ItemText\n//\n\ntype ListboxItemTextProps = ThemedClassName<ComponentPropsWithoutRef<'p'>> & {\n /** `muted` reads as secondary text, as Typography's tone. */\n tone?: 'default' | 'muted';\n};\n\n/**\n * The row's label, truncated to one line; the option's `label` by default, which typeahead matches either way. Its id\n * names the row's controls (a Remove button, a disclosure caret).\n */\nconst ListboxItemText = forwardRef<HTMLParagraphElement, ListboxItemTextProps>(\n ({ classNames, tone, children, ...props }, forwardedRef) => {\n const item = useItem('ItemText');\n const row = useContext(RowContext);\n return (\n <p\n id={row?.textId}\n {...props}\n data-scope='listbox'\n data-part='item-text'\n data-truncate=''\n data-tone={tone === 'muted' ? tone : undefined}\n className={mx(recipes.typography(), recipes.listboxItemText(), classNames)}\n ref={forwardedRef}\n >\n {children ?? item.label}\n </p>\n );\n },\n);\n\nListboxItemText.displayName = 'Listbox.ItemText';\n\n//\n// ItemDescription\n//\n\ntype ListboxItemDescriptionProps = ThemedClassName<ComponentPropsWithoutRef<'p'>>;\n\n/** A second line under the text in `--color-fg-muted`; the option's `description` by default. */\nconst ListboxItemDescription = forwardRef<HTMLParagraphElement, ListboxItemDescriptionProps>(\n ({ classNames, children, ...props }, forwardedRef) => {\n const item = useItem('ItemDescription');\n return (\n <p\n {...props}\n data-scope='listbox'\n data-part='item-description'\n data-truncate=''\n data-tone='muted'\n className={mx(recipes.typography(), recipes.listboxItemDescription(), classNames)}\n ref={forwardedRef}\n >\n {children ?? item.description}\n </p>\n );\n },\n);\n\nListboxItemDescription.displayName = 'Listbox.ItemDescription';\n\n//\n// ItemIndicator\n//\n\ntype ListboxItemIndicatorProps = ThemedClassName<ComponentPropsWithoutRef<'div'>>;\n\n/** Shown while its item is selected: a check by default, in an icon-sized cell. */\nconst ListboxItemIndicator = forwardRef<HTMLDivElement, ListboxItemIndicatorProps>(\n ({ classNames, children, ...props }, forwardedRef) => {\n const api = useListboxContext();\n const item = useItem('ItemIndicator');\n return (\n <div\n {...api.getItemIndicatorProps({ item })}\n {...props}\n className={mx(recipes.listboxItemIndicator(), classNames)}\n ref={forwardedRef}\n >\n {children ?? <Icon.Icon icon='ph--check--regular' />}\n </div>\n );\n },\n);\n\nListboxItemIndicator.displayName = 'Listbox.ItemIndicator';\n\n//\n// ItemGroup\n//\n\ntype ListboxItemGroupProps = ThemedClassName<ComponentPropsWithoutRef<'div'>>;\n\n/**\n * A `group` of items named by the `ItemGroupLabel` inside it; an inheriting Container, so its rows keep the list's\n * rails. Order groups as the Root's `items` are ordered, or keyboard order and reading order diverge.\n */\nconst ListboxItemGroup = forwardRef<HTMLDivElement, ListboxItemGroupProps>(\n ({ classNames, style, children, ...props }, forwardedRef) => {\n const { style: containerStyle, ...attributes } = containerAttributes({});\n return (\n <ListboxPrimitive.ItemGroup\n {...props}\n {...attributes}\n style={{ ...containerStyle, ...style }}\n className={mx(recipes.container(), classNames)}\n data-scope='listbox'\n data-part='item-group'\n ref={forwardedRef}\n >\n {children}\n </ListboxPrimitive.ItemGroup>\n );\n },\n);\n\nListboxItemGroup.displayName = 'Listbox.ItemGroup';\n\n//\n// ItemGroupLabel\n//\n\ntype ListboxItemGroupLabelProps = ThemedClassName<ComponentPropsWithoutRef<'div'>>;\n\n/** A small caption naming its group, like `Select.ItemGroupLabel`. */\nconst ListboxItemGroupLabel = forwardRef<HTMLDivElement, ListboxItemGroupLabelProps>(\n ({ classNames, ...props }, forwardedRef) => (\n <ListboxPrimitive.ItemGroupLabel\n {...props}\n data-scope='listbox'\n data-part='item-group-label'\n className={mx(recipes.popupGroupLabel(), classNames)}\n ref={forwardedRef}\n />\n ),\n);\n\nListboxItemGroupLabel.displayName = 'Listbox.ItemGroupLabel';\n\n//\n// useContext\n//\n\ntype ListboxContext = UseListboxContext<ListboxOption>;\n\n/** Ark's listbox api (`value`, `selectedItems`, `setValue`, `clearValue`, …) for parts inside the Root, e.g. a detail pane. */\nconst useListboxRootContext = (): ListboxContext => useListboxContext();\nexport type {\n ListboxContentProps as ContentProps,\n ListboxContext as Context,\n ListboxEmptyProps as EmptyProps,\n ListboxItemDescriptionProps as ItemDescriptionProps,\n ListboxItemGroupLabelProps as ItemGroupLabelProps,\n ListboxItemGroupProps as ItemGroupProps,\n ListboxItemIconProps as ItemIconProps,\n ListboxItemIndicatorProps as ItemIndicatorProps,\n ListboxItemProps as ItemProps,\n ListboxItemTextProps as ItemTextProps,\n ListboxLabelProps as LabelProps,\n ListboxRootProps as RootProps,\n};\n\nexport {\n ListboxContent as Content,\n ListboxEmpty as Empty,\n ListboxItem as Item,\n ListboxItemDescription as ItemDescription,\n ListboxItemGroup as ItemGroup,\n ListboxItemGroupLabel as ItemGroupLabel,\n ListboxItemIcon as ItemIcon,\n ListboxItemIndicator as ItemIndicator,\n ListboxItemText as ItemText,\n ListboxLabel as Label,\n ListboxRoot as Root,\n useListboxRootContext as useContext,\n};\nexport type { ListboxOption as Option, ListboxSelectionMode as SelectionMode };\nexport { RowContext, type RowContextValue } from './grid.ts';\nexport * from './virtual.tsx';\n"],"mappings":"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"}
|
package/dist/lib/Menu.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-rolldown-runtime.mjs";
|
|
2
2
|
import { t as recipes } from "./chunk-recipes.mjs";
|
|
3
|
-
import { Icon } from "./Icon.mjs";
|
|
4
3
|
import { n as composableProps, t as composable } from "./chunk-slots.mjs";
|
|
4
|
+
import { Icon } from "./Icon.mjs";
|
|
5
5
|
import { n as popupPositioning, r as usePopupSize, t as PopupScroll } from "./chunk-PopupScroll.mjs";
|
|
6
6
|
import { createContext, forwardRef, useContext, useId, useState } from "react";
|
|
7
7
|
import { mx } from "@dxos/ui-theme";
|
package/dist/lib/Select.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-rolldown-runtime.mjs";
|
|
2
2
|
import { t as recipes } from "./chunk-recipes.mjs";
|
|
3
|
-
import { Icon } from "./Icon.mjs";
|
|
4
3
|
import { n as composableProps, t as composable } from "./chunk-slots.mjs";
|
|
4
|
+
import { Icon } from "./Icon.mjs";
|
|
5
5
|
import { n as useToolbarItem } from "./chunk-toolbar-context.mjs";
|
|
6
6
|
import { n as popupPositioning, r as usePopupSize, t as PopupScroll } from "./chunk-PopupScroll.mjs";
|
|
7
7
|
import { t as Separator } from "./chunk-Separator.mjs";
|