@mortenbrudvik/waveui 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +274 -24
- package/dist/components/data-display/List.cjs +1 -1
- package/dist/components/data-display/List.cjs.map +1 -1
- package/dist/components/data-display/List.mjs +405 -219
- package/dist/components/data-display/List.mjs.map +1 -1
- package/dist/components/data-display/List.registry.cjs +1 -1
- package/dist/components/data-display/List.registry.cjs.map +1 -1
- package/dist/components/data-display/List.registry.mjs +12 -6
- package/dist/components/data-display/List.registry.mjs.map +1 -1
- package/dist/components/input/Combobox.cjs +1 -1
- package/dist/components/input/Combobox.cjs.map +1 -1
- package/dist/components/input/Combobox.mjs +163 -148
- package/dist/components/input/Combobox.mjs.map +1 -1
- package/dist/components/input/Dropdown.cjs +1 -1
- package/dist/components/input/Dropdown.cjs.map +1 -1
- package/dist/components/input/Dropdown.mjs +132 -117
- package/dist/components/input/Dropdown.mjs.map +1 -1
- package/dist/components/input/Listbox.cjs +1 -1
- package/dist/components/input/Listbox.cjs.map +1 -1
- package/dist/components/input/Listbox.mjs +90 -78
- package/dist/components/input/Listbox.mjs.map +1 -1
- package/dist/components/input/Option.cjs +1 -1
- package/dist/components/input/Option.cjs.map +1 -1
- package/dist/components/input/Option.data.cjs +2 -0
- package/dist/components/input/Option.data.cjs.map +1 -0
- package/dist/components/input/Option.data.mjs +114 -0
- package/dist/components/input/Option.data.mjs.map +1 -0
- package/dist/components/input/Option.mjs +21 -17
- package/dist/components/input/Option.mjs.map +1 -1
- package/dist/components/layout/FlatTree.cjs +2 -0
- package/dist/components/layout/FlatTree.cjs.map +1 -0
- package/dist/components/layout/FlatTree.context.cjs +2 -0
- package/dist/components/layout/FlatTree.context.cjs.map +1 -0
- package/dist/components/layout/FlatTree.context.mjs +20 -0
- package/dist/components/layout/FlatTree.context.mjs.map +1 -0
- package/dist/components/layout/FlatTree.item.cjs +2 -0
- package/dist/components/layout/FlatTree.item.cjs.map +1 -0
- package/dist/components/layout/FlatTree.item.mjs +90 -0
- package/dist/components/layout/FlatTree.item.mjs.map +1 -0
- package/dist/components/layout/FlatTree.mjs +276 -0
- package/dist/components/layout/FlatTree.mjs.map +1 -0
- package/dist/components/layout/Tree.core.cjs +1 -1
- package/dist/components/layout/Tree.core.cjs.map +1 -1
- package/dist/components/layout/Tree.core.mjs +166 -165
- package/dist/components/layout/Tree.core.mjs.map +1 -1
- package/dist/components/layout/Tree.selection.cjs +1 -1
- package/dist/components/layout/Tree.selection.cjs.map +1 -1
- package/dist/components/layout/Tree.selection.mjs +11 -57
- package/dist/components/layout/Tree.selection.mjs.map +1 -1
- package/dist/components/layout/useFlatTree.cjs +2 -0
- package/dist/components/layout/useFlatTree.cjs.map +1 -0
- package/dist/components/layout/useFlatTree.mjs +83 -0
- package/dist/components/layout/useFlatTree.mjs.map +1 -0
- package/dist/components/table/DataGrid.cjs +1 -1
- package/dist/components/table/DataGrid.cjs.map +1 -1
- package/dist/components/table/DataGrid.context.cjs +2 -0
- package/dist/components/table/DataGrid.context.cjs.map +1 -0
- package/dist/components/table/DataGrid.context.mjs +74 -0
- package/dist/components/table/DataGrid.context.mjs.map +1 -0
- package/dist/components/table/DataGrid.header.cjs +2 -0
- package/dist/components/table/DataGrid.header.cjs.map +1 -0
- package/dist/components/table/DataGrid.header.mjs +168 -0
- package/dist/components/table/DataGrid.header.mjs.map +1 -0
- package/dist/components/table/DataGrid.mjs +488 -591
- package/dist/components/table/DataGrid.mjs.map +1 -1
- package/dist/components/table/DataGrid.sizing.cjs +2 -0
- package/dist/components/table/DataGrid.sizing.cjs.map +1 -0
- package/dist/components/table/DataGrid.sizing.mjs +187 -0
- package/dist/components/table/DataGrid.sizing.mjs.map +1 -0
- package/dist/components/table/DataGrid.store.cjs +2 -0
- package/dist/components/table/DataGrid.store.cjs.map +1 -0
- package/dist/components/table/DataGrid.store.mjs +108 -0
- package/dist/components/table/DataGrid.store.mjs.map +1 -0
- package/dist/components/table/DataGrid.window.cjs +2 -0
- package/dist/components/table/DataGrid.window.cjs.map +1 -0
- package/dist/components/table/DataGrid.window.mjs +165 -0
- package/dist/components/table/DataGrid.window.mjs.map +1 -0
- package/dist/components/table/Table.cjs +1 -1
- package/dist/components/table/Table.cjs.map +1 -1
- package/dist/components/table/Table.mjs +74 -72
- package/dist/components/table/Table.mjs.map +1 -1
- package/dist/components/table/Table.resize.cjs +2 -0
- package/dist/components/table/Table.resize.cjs.map +1 -0
- package/dist/components/table/Table.resize.mjs +98 -0
- package/dist/components/table/Table.resize.mjs.map +1 -0
- package/dist/components/table/useGridNavigation.cjs +1 -1
- package/dist/components/table/useGridNavigation.cjs.map +1 -1
- package/dist/components/table/useGridNavigation.mjs +343 -140
- package/dist/components/table/useGridNavigation.mjs.map +1 -1
- package/dist/components/table/useTableColumnSizing.cjs +2 -0
- package/dist/components/table/useTableColumnSizing.cjs.map +1 -0
- package/dist/components/table/useTableColumnSizing.mjs +76 -0
- package/dist/components/table/useTableColumnSizing.mjs.map +1 -0
- package/dist/components/table/useTableFeatures.cjs +2 -0
- package/dist/components/table/useTableFeatures.cjs.map +1 -0
- package/dist/components/table/useTableFeatures.mjs +110 -0
- package/dist/components/table/useTableFeatures.mjs.map +1 -0
- package/dist/components/table/useTableSelection.cjs +2 -0
- package/dist/components/table/useTableSelection.cjs.map +1 -0
- package/dist/components/table/useTableSelection.mjs +39 -0
- package/dist/components/table/useTableSelection.mjs.map +1 -0
- package/dist/components/table/useTableSort.cjs +2 -0
- package/dist/components/table/useTableSort.cjs.map +1 -0
- package/dist/components/table/useTableSort.mjs +58 -0
- package/dist/components/table/useTableSort.mjs.map +1 -0
- package/dist/hooks/useActiveDescendant.cjs +1 -1
- package/dist/hooks/useActiveDescendant.cjs.map +1 -1
- package/dist/hooks/useActiveDescendant.mjs +33 -25
- package/dist/hooks/useActiveDescendant.mjs.map +1 -1
- package/dist/hooks/useListbox.cjs +1 -1
- package/dist/hooks/useListbox.cjs.map +1 -1
- package/dist/hooks/useListbox.mjs +140 -130
- package/dist/hooks/useListbox.mjs.map +1 -1
- package/dist/hooks/useRovingTabIndex.cjs +1 -1
- package/dist/hooks/useRovingTabIndex.cjs.map +1 -1
- package/dist/hooks/useRovingTabIndex.mjs +1 -1
- package/dist/hooks/useRovingTabIndex.mjs.map +1 -1
- package/dist/hooks/useSelection.cjs +2 -0
- package/dist/hooks/useSelection.cjs.map +1 -0
- package/dist/hooks/useSelection.mjs +57 -0
- package/dist/hooks/useSelection.mjs.map +1 -0
- package/dist/hooks/useVirtualWindow.cjs +2 -0
- package/dist/hooks/useVirtualWindow.cjs.map +1 -0
- package/dist/hooks/useVirtualWindow.mjs +102 -0
- package/dist/hooks/useVirtualWindow.mjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1542 -53
- package/dist/index.d.ts +1542 -53
- package/dist/index.mjs +147 -136
- package/dist/lib/classNames.cjs +1 -1
- package/dist/lib/classNames.cjs.map +1 -1
- package/dist/lib/classNames.mjs +68 -49
- package/dist/lib/classNames.mjs.map +1 -1
- package/dist/lib/columnSizing.cjs +2 -0
- package/dist/lib/columnSizing.cjs.map +1 -0
- package/dist/lib/columnSizing.mjs +27 -0
- package/dist/lib/columnSizing.mjs.map +1 -0
- package/dist/lib/flatTree.cjs +2 -0
- package/dist/lib/flatTree.cjs.map +1 -0
- package/dist/lib/flatTree.mjs +118 -0
- package/dist/lib/flatTree.mjs.map +1 -0
- package/dist/lib/selection.cjs +2 -0
- package/dist/lib/selection.cjs.map +1 -0
- package/dist/lib/selection.mjs +28 -0
- package/dist/lib/selection.mjs.map +1 -0
- package/dist/lib/treeCascade.cjs +2 -0
- package/dist/lib/treeCascade.cjs.map +1 -0
- package/dist/lib/treeCascade.mjs +52 -0
- package/dist/lib/treeCascade.mjs.map +1 -0
- package/dist/lib/virtual.cjs +2 -0
- package/dist/lib/virtual.cjs.map +1 -0
- package/dist/lib/virtual.mjs +54 -0
- package/dist/lib/virtual.mjs.map +1 -0
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTableFeatures.mjs","names":["createTableFeaturePlugin","implementation","implementationOf","plugin","NO_PLUGINS","NO_SELECTED_IDS","doNothing","indexRowId","index","INERT_SORT","INERT_SELECTION","INERT_COLUMN_SIZING","pluginFor","plugins","feature","duplicateFeatureOf","seen","useTableFeatures","options","sortPlugin","selectionPlugin","columnSizingPlugin","sortRows","baseRows","useMemo","items","getRowId","item","rows","columns","getRows","useCallback","state","next","duplicateFeature","warnOnce","pluginCount","pluginCountRef","useRef","useEffect","previous","isDev","rowId"],"sources":["../../../src/components/table/useTableFeatures.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef } from 'react';\nimport type { CSSProperties } from 'react';\nimport { isDev, warnOnce } from '../../lib/dev';\nimport type { SelectionMode } from '../../lib/types';\nimport type { DataGridColumn, DataGridSort, SortDirection } from './DataGrid';\n\n/** One row of {@link useTableFeatures}: an item of `items` with its id. */\nexport interface TableRowData<T> {\n /** The item. */\n item: T;\n /** The row's id, from `getRowId` (by default the index in `items`, as a string). */\n rowId: string;\n /** The item's index in `items`. Sorting moves the row, not this index. */\n index: number;\n}\n\n/** The sort of {@link useTableFeatures} (`state.sort`), filled by `useTableSort`. */\nexport interface TableSortState {\n /** The sorted column and its direction, or `null` while the rows are in the order of `items`. */\n value: DataGridSort | null;\n /** Sorts by the column `columnId` in `direction`. */\n setColumnSort(columnId: string, direction: SortDirection, event?: Event): void;\n /**\n * Sorts by the column `columnId`, as a sortable header of DataGrid does: ascending, or descending\n * when the column is already sorted ascending.\n */\n toggleColumnSort(columnId: string, event?: Event): void;\n /** The direction of the column `columnId` while the rows are sorted by it, else `undefined`. */\n getSortDirection(columnId: string): SortDirection | undefined;\n}\n\n/**\n * The selection of {@link useTableFeatures} (`state.selection`), filled by `useTableSelection`. The\n * selection is a set of row ids held apart from the rendered rows: rows that are not rendered keep\n * their state, and ids whose item is not in `items` (filtered out, on another page) stay selected.\n */\nexport interface TableSelectionState {\n /** `single` keeps at most one row selected; `multiple` selects rows independently. */\n selectionMode: SelectionMode;\n /** The selected row ids, ids whose item is not in `items` included. */\n selectedItems: ReadonlySet<string>;\n /**\n * Whether every selectable row of `items` is selected (and there is one): rows that\n * `isDisabled` marks do not count, rows that are not rendered do.\n */\n allRowsSelected: boolean;\n /**\n * Whether a row of `items` is selected while not every selectable one is (the mixed state of a\n * select-all checkbox). A selected disabled row counts; ids outside `items` do not.\n */\n someRowsSelected: boolean;\n /** Whether the row `id` is selected. */\n isRowSelected(id: string): boolean;\n /** Selects or deselects the row `id` (`multiple`); selects it (`single`). */\n toggleRow(id: string, event?: Event): void;\n /** Selects the row `id`; in `single` mode it replaces the selection. */\n selectRow(id: string, event?: Event): void;\n /** Deselects the row `id`. */\n deselectRow(id: string, event?: Event): void;\n /**\n * Selects every selectable row of `items` when not all of them are selected, else deselects\n * them. A disabled row keeps its state, and ids outside `items` stay selected. It does nothing in\n * `single` mode.\n */\n toggleAllRows(event?: Event): void;\n /** Empties the selection, ids outside `items` included. */\n clearRows(event?: Event): void;\n}\n\n/**\n * What a resize handle needs from the table state, as returned by\n * `columnSizing.getResizeHandleProps(columnId)`: the column's width and minimum, the callback\n * that sets a new width and the one that resets it. `Table.ResizeHandle` takes these props (and a few more).\n */\nexport interface TableResizeHandleBindings {\n /** The column's current width in pixels. */\n width: number;\n /** The narrowest the column can be, in pixels. */\n minWidth: number;\n /**\n * Sets the column's width in pixels (held at or above `minWidth`); `details.event` is the event\n * behind it (without `details`, an untrusted `resize` event is reported).\n */\n onResize(width: number, details?: { event: Event }): void;\n /**\n * Sets the column back to its initial width (a double click on the handle). It changes nothing\n * while no width is state, since the table is then in its initial layout.\n */\n onReset?(details: { event: Event }): void;\n}\n\n/**\n * The column widths of {@link useTableFeatures} (`state.columnSizing`), filled by\n * `useTableColumnSizing`. Without that plugin no width is set and the functions do nothing.\n */\nexport interface TableColumnSizingState {\n /**\n * The width of every column in pixels, by `columnId`: the width that is state, else the\n * column's initial width.\n */\n columnWidths: Readonly<Record<string, number>>;\n /** Whether a width is state (set, controlled or given as a default). */\n isSized: boolean;\n /** The width of the column `columnId` in pixels, or `undefined` when it is no column. */\n getColumnWidth(columnId: string): number | undefined;\n /**\n * Sets the width of the column `columnId` in pixels. Without `event`, the change's details carry\n * an untrusted `resize` event with no target.\n */\n setColumnWidth(columnId: string, width: number, event?: Event): void;\n /** Props for the table element: its fixed layout and width style. */\n getTableProps(): { style: CSSProperties };\n /** Props for the column's `<col>` element: its width style. */\n getColumnProps(columnId: string): { style: CSSProperties };\n /** Props for the resize handle of the column `columnId`. */\n getResizeHandleProps(columnId: string): TableResizeHandleBindings;\n}\n\n/** The state that {@link useTableFeatures} returns. */\nexport interface TableFeaturesState<T> {\n /** The items, as given. */\n items: readonly T[];\n /** The columns, as given. */\n columns: readonly DataGridColumn<T>[];\n /** The row id of an item: `getRowId` as given, else the index as a string. */\n getRowId: (item: T, index: number) => string;\n /**\n * The rows in display order: sorted while `useTableSort` sorts them, else in the order of\n * `items`. The same array is returned until `items`, `getRowId`, `columns` or the sort change.\n */\n getRows(): readonly TableRowData<T>[];\n /** The sort; without `useTableSort` it is `null` and its functions do nothing. */\n sort: TableSortState;\n /** The selection; without `useTableSelection` nothing is selected and its functions do nothing. */\n selection: TableSelectionState;\n /** The column widths; without `useTableColumnSizing` no width is set. */\n columnSizing: TableColumnSizingState;\n}\n\n/** Options of {@link useTableFeatures}. */\nexport interface UseTableFeaturesOptions<T> {\n /** The items, one row each. Every item counts for select-all, rendered or not. */\n items: readonly T[];\n /** The columns, from `createTableColumn`. The sort uses the `compare` of the sorted column. */\n columns: readonly DataGridColumn<T>[];\n /**\n * The row id of an item: unique, and the same for an item across renders (selection and the\n * selection callbacks use it). Pass a stable function (defined outside the component, or\n * memoised): the rows are rebuilt when it changes.\n * @default (item, index) => String(index)\n */\n getRowId?: (item: T, index: number) => string;\n}\n\ndeclare const tableFeaturePluginBrand: unique symbol;\n\n/**\n * A feature of {@link useTableFeatures}, returned by `useTableSort`, `useTableSelection` and\n * `useTableColumnSizing`, which hold the feature's state where they are called. The object is\n * opaque. It keeps its identity while the feature's state and the options it reads keep theirs; a\n * new one costs `useTableFeatures` a recompute of its state, and of the rows only when the sort\n * changed. `T` is the item type the plugin reads (a selection's `isDisabled`): a plugin that reads\n * no item, such as the sort, fits every table.\n */\nexport interface TableFeaturePlugin<T> {\n /** The brand of a plugin: only the plugin hooks create one. */\n readonly [tableFeaturePluginBrand]: (item: T) => void;\n}\n\n/** The slot of the state that a plugin fills. */\ntype TableFeatureName = 'sort' | 'selection' | 'columnSizing';\n\n/**\n * What a plugin does: the run-time content of a {@link TableFeaturePlugin}, read only by\n * {@link useTableFeatures} (module-only).\n */\nexport interface TableFeaturePluginImplementation<T> {\n /** The slot it fills; a second plugin for the same slot warns. */\n feature: TableFeatureName;\n /**\n * Puts the rows in display order (the sort plugin only). The rows are rebuilt when this function\n * changes, so it keeps its identity while the order it gives cannot change, whatever happens to\n * the plugin object.\n */\n sortRows?: (\n rows: readonly TableRowData<T>[],\n columns: readonly DataGridColumn<T>[],\n ) => readonly TableRowData<T>[];\n /** Returns the state with the plugin's slot filled. Pure. */\n extend: (state: TableFeaturesState<T>) => TableFeaturesState<T>;\n}\n\n/** Wraps a plugin's implementation as the opaque plugin (module-only, for the plugin hooks). */\nexport function createTableFeaturePlugin<T>(\n implementation: TableFeaturePluginImplementation<T>,\n): TableFeaturePlugin<T> {\n // The brand is a type only: the plugin is its implementation object.\n return implementation as unknown as TableFeaturePlugin<T>;\n}\n\nfunction implementationOf<T>(plugin: TableFeaturePlugin<T>): TableFeaturePluginImplementation<T> {\n return plugin as unknown as TableFeaturePluginImplementation<T>;\n}\n\nconst NO_PLUGINS: readonly never[] = [];\n\nconst NO_SELECTED_IDS: ReadonlySet<string> = new Set();\n\nconst doNothing = (): void => {};\n\nconst indexRowId = (_item: unknown, index: number): string => String(index);\n\nconst INERT_SORT: TableSortState = {\n value: null,\n setColumnSort: doNothing,\n toggleColumnSort: doNothing,\n getSortDirection: () => undefined,\n};\n\nconst INERT_SELECTION: TableSelectionState = {\n selectionMode: 'multiple',\n selectedItems: NO_SELECTED_IDS,\n allRowsSelected: false,\n someRowsSelected: false,\n isRowSelected: () => false,\n toggleRow: doNothing,\n selectRow: doNothing,\n deselectRow: doNothing,\n toggleAllRows: doNothing,\n clearRows: doNothing,\n};\n\nconst INERT_COLUMN_SIZING: TableColumnSizingState = {\n columnWidths: {},\n isSized: false,\n getColumnWidth: () => undefined,\n setColumnWidth: doNothing,\n getTableProps: () => ({ style: {} }),\n getColumnProps: () => ({ style: {} }),\n getResizeHandleProps: () => ({ width: 0, minWidth: 0, onResize: doNothing }),\n};\n\n/** The last plugin of `plugins` that fills `feature` (a feature given twice warns). */\nfunction pluginFor<T>(\n plugins: readonly TableFeaturePlugin<T>[],\n feature: TableFeatureName,\n): TableFeaturePluginImplementation<T> | undefined {\n for (let index = plugins.length - 1; index >= 0; index -= 1) {\n const implementation = implementationOf(plugins[index]!);\n if (implementation.feature === feature) return implementation;\n }\n return undefined;\n}\n\nfunction duplicateFeatureOf<T>(plugins: readonly TableFeaturePlugin<T>[]): string | undefined {\n const seen = new Set<TableFeatureName>();\n for (const plugin of plugins) {\n const { feature } = implementationOf(plugin);\n if (seen.has(feature)) return feature;\n seen.add(feature);\n }\n return undefined;\n}\n\n/**\n * Headless table state for a table built from the static `Table` parts: the rows of `items`, and\n * the features its plugins add (`useTableSort`, `useTableSelection`, `useTableColumnSizing`). Each\n * plugin fills its own part of the state (`sort`, `selection`, `columnSizing`), so the order of\n * the array does not matter. The state follows the data, not the rendered rows, so a table that\n * renders a page or a window of its rows keeps select-all and the sort over every item.\n *\n * Call each plugin hook unconditionally, in the array or before it, and pass the same plugins on\n * every render; to turn a feature off, change its options. Options may be written inline: a plugin\n * whose options change identity on every render (an inline `isDisabled`) only makes the state be\n * recomputed on every render, while `getRows()` keeps its array until `items`, `getRowId`,\n * `columns` or the sort change. A feature given twice (the last plugin of it is used), a plugin\n * count that changes between renders and repeated row ids are development warnings.\n *\n * @example\n * const { getRows, sort, selection } = useTableFeatures({ items, columns, getRowId }, [\n * useTableSort({ defaultSort: { columnId: 'name', direction: 'ascending' } }),\n * useTableSelection({ selectionMode: 'multiple' }),\n * ]);\n */\nexport function useTableFeatures<T>(\n options: UseTableFeaturesOptions<T>,\n plugins: readonly TableFeaturePlugin<T>[] = NO_PLUGINS,\n): TableFeaturesState<T> {\n const { items, columns, getRowId = indexRowId } = options;\n\n // One variable per feature, so the memos below have fixed dependencies: a new array literal on\n // every render changes nothing, and a plugin that changes identity (an inline option) costs a\n // recompute of the state, never a state update during render. The rows depend on the sort's\n // `sortRows` alone, which the sort plugin keeps while the sort is the same.\n const sortPlugin = pluginFor(plugins, 'sort');\n const selectionPlugin = pluginFor(plugins, 'selection');\n const columnSizingPlugin = pluginFor(plugins, 'columnSizing');\n const sortRows = sortPlugin?.sortRows;\n\n const baseRows = useMemo(\n () => items.map((item, index) => ({ item, rowId: getRowId(item, index), index })),\n [items, getRowId],\n );\n\n const rows = useMemo(\n () => (sortRows ? sortRows(baseRows, columns) : baseRows),\n [baseRows, columns, sortRows],\n );\n\n const getRows = useCallback(() => rows, [rows]);\n\n const state = useMemo(() => {\n let next: TableFeaturesState<T> = {\n items,\n columns,\n getRowId,\n getRows,\n sort: INERT_SORT,\n selection: INERT_SELECTION,\n columnSizing: INERT_COLUMN_SIZING,\n };\n for (const plugin of [sortPlugin, selectionPlugin, columnSizingPlugin]) {\n if (plugin) next = plugin.extend(next);\n }\n return next;\n }, [items, columns, getRowId, getRows, sortPlugin, selectionPlugin, columnSizingPlugin]);\n\n const duplicateFeature = duplicateFeatureOf(plugins);\n useEffect(() => {\n if (duplicateFeature === undefined) return;\n warnOnce(\n 'useTableFeatures:duplicate-plugin',\n `useTableFeatures: more than one plugin fills \\`${duplicateFeature}\\`. Pass each feature ` +\n 'once; the last plugin of a feature is used.',\n );\n }, [duplicateFeature]);\n\n const pluginCount = plugins.length;\n const pluginCountRef = useRef(pluginCount);\n useEffect(() => {\n const previous = pluginCountRef.current;\n pluginCountRef.current = pluginCount;\n if (previous === pluginCount) return;\n warnOnce(\n 'useTableFeatures:plugin-count',\n `useTableFeatures: the number of plugins changed between renders (from ${previous} to ` +\n `${pluginCount}). Pass the same plugins on every render and change their options ` +\n 'instead; the new plugins are applied.',\n );\n }, [pluginCount]);\n\n useEffect(() => {\n if (!isDev) return;\n const seen = new Set<string>();\n for (const { rowId } of baseRows) {\n if (seen.has(rowId)) {\n warnOnce(\n 'useTableFeatures:duplicate-row-id',\n `useTableFeatures: more than one item has the row id \"${rowId}\". Row ids must be ` +\n 'unique: give getRowId an id that differs for each item.',\n );\n return;\n }\n seen.add(rowId);\n }\n }, [baseRows]);\n\n return state;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";const e=require("../../lib/selection.cjs"),t=require("../../hooks/useSelection.cjs"),n=require("./useTableFeatures.cjs");let r=require("react");function i(i){let{isDisabled:a,...o}=i,s=t.useSelection(o);return(0,r.useMemo)(()=>n.createTableFeaturePlugin({feature:`selection`,extend:t=>{let n=[],r=[];t.items.forEach((e,i)=>{let o=t.getRowId(e,i);n.push(o),a?.(e,i)||r.push(o)});let{all:i,some:o}=e.summarizeSelection(s.selectedItems,n,r),c={selectionMode:s.selectionMode,selectedItems:s.selectedItems,allRowsSelected:i,someRowsSelected:o,isRowSelected:s.isSelected,toggleRow:s.toggle,selectRow:s.select,deselectRow:s.deselect,toggleAllRows:e=>s.toggleAll(r,e),clearRows:s.clear};return{...t,selection:c}}}),[s,a])}exports.useTableSelection=i;
|
|
2
|
+
//# sourceMappingURL=useTableSelection.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTableSelection.cjs","names":["useSelection","useMemo","createTableFeaturePlugin","summarizeSelection","require_selection","require_useSelection","require_useTableFeatures","react","useTableSelection","selectionOptions","options","selection","universe","selectable","state","id","item","index","isDisabled","selectionState","all","some","event"],"sources":["../../../src/components/table/useTableSelection.ts"],"sourcesContent":["import { useMemo } from 'react';\nimport { useSelection, type UseSelectionOptions } from '../../hooks/useSelection';\nimport { summarizeSelection } from '../../lib/selection';\nimport {\n createTableFeaturePlugin,\n type TableFeaturePlugin,\n type TableSelectionState,\n} from './useTableFeatures';\n\n/** Options of {@link useTableSelection}: those of `useSelection`, plus the disabled rows. */\nexport interface UseTableSelectionOptions<T> extends UseSelectionOptions {\n /**\n * Whether the selection of an item cannot change through select-all: `toggleAllRows` skips it,\n * and `allRowsSelected` does not wait for it. A disabled row keeps its state. The single-row\n * functions do not read it: disable the row's own control. Called for every item of `items`\n * whenever `useTableFeatures` computes its state: when the selection, `items`, `getRowId`,\n * `columns`, the sort or another plugin's state (the column widths) change, and when this\n * function changes. A function written inline is a new one on every render, so the state is\n * then computed, and this function called for every item, on every render (no more: no extra\n * render, and `getRows()` keeps its array); define it outside the component or memoise it to\n * call it only on those changes.\n */\n isDisabled?: (item: T, index: number) => boolean;\n}\n\n/**\n * The selection plugin of `useTableFeatures`: a set of row ids (`selectedItems`, controlled, or\n * `defaultSelectedItems`), and `state.selection` with select-all over every item of `items`,\n * rendered or not. Select-all selects every selectable row and leaves the other ids as they are;\n * deselecting all removes only the selectable ids of `items`, so a selected disabled row and ids\n * selected on another page or filtered out stay selected (`clearRows` empties the selection).\n * `onSelectedItemsChange` fires once per change with a new array of every selected id. Call it\n * unconditionally and pass the plugin to `useTableFeatures`.\n *\n * @example\n * const { selection } = useTableFeatures({ items, columns, getRowId }, [\n * useTableSelection({ selectionMode: 'multiple', onSelectedItemsChange: setSelected }),\n * ]);\n */\nexport function useTableSelection<T>(options: UseTableSelectionOptions<T>): TableFeaturePlugin<T> {\n const { isDisabled, ...selectionOptions } = options;\n const selection = useSelection(selectionOptions);\n\n return useMemo(\n () =>\n createTableFeaturePlugin<T>({\n feature: 'selection',\n extend: (state) => {\n // The universe is every row id of `items`, in their order; the selectable ids leave out\n // the disabled rows.\n const universe: string[] = [];\n const selectable: string[] = [];\n state.items.forEach((item, index) => {\n const id = state.getRowId(item, index);\n universe.push(id);\n if (!isDisabled?.(item, index)) selectable.push(id);\n });\n const { all, some } = summarizeSelection(selection.selectedItems, universe, selectable);\n const selectionState: TableSelectionState = {\n selectionMode: selection.selectionMode,\n selectedItems: selection.selectedItems,\n allRowsSelected: all,\n someRowsSelected: some,\n isRowSelected: selection.isSelected,\n toggleRow: selection.toggle,\n selectRow: selection.select,\n deselectRow: selection.deselect,\n // Over the selectable ids: all of them selected removes them (a disabled row and ids\n // outside `items` keep their state), else adds them.\n toggleAllRows: (event) => selection.toggleAll(selectable, event),\n clearRows: selection.clear,\n };\n return { ...state, selection: selectionState };\n },\n }),\n [selection, isDisabled],\n );\n}\n"],"mappings":"6JAuCA,SAAgBQ,EAAqB,EAA6D,CAChG,GAAM,CAAE,aAAY,GAAGC,GAAqBC,EACtCC,EAAYX,EAAAA,aAAaS,CAAgB,EAE/C,OAAA,EAAOR,EAAAA,QAAAA,KAEHC,EAAAA,yBAA4B,CAC1B,QAAS,YACT,OAAS,GAAU,CAGjB,IAAMU,EAAqB,CAAC,EACtBC,EAAuB,CAAC,EAC9BC,EAAM,MAAM,SAAS,EAAM,IAAU,CACnC,IAAMC,EAAKD,EAAM,SAASE,EAAMC,CAAK,EACrCL,EAAS,KAAKG,CAAE,EACXG,IAAaF,EAAMC,CAAK,GAAGJ,EAAW,KAAKE,CAAE,CACpD,CAAC,EACD,GAAM,CAAE,MAAK,QAASZ,EAAAA,mBAAmBQ,EAAU,cAAeC,EAAUC,CAAU,EAChFM,EAAsC,CAC1C,cAAeR,EAAU,cACzB,cAAeA,EAAU,cACzB,gBAAiBS,EACjB,iBAAkBC,EAClB,cAAeV,EAAU,WACzB,UAAWA,EAAU,OACrB,UAAWA,EAAU,OACrB,YAAaA,EAAU,SAGvB,cAAgB,GAAUA,EAAU,UAAUE,EAAYS,CAAK,EAC/D,UAAWX,EAAU,KACvB,EACA,MAAO,CAAE,GAAGG,EAAO,UAAWK,CAAe,CAC/C,CACF,CAAC,EACH,CAACR,EAAWO,CAAU,CACxB,CACF"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { summarizeSelection as e } from "../../lib/selection.mjs";
|
|
3
|
+
import { useSelection as t } from "../../hooks/useSelection.mjs";
|
|
4
|
+
import { createTableFeaturePlugin as n } from "./useTableFeatures.mjs";
|
|
5
|
+
import { useMemo as r } from "react";
|
|
6
|
+
//#region src/components/table/useTableSelection.ts
|
|
7
|
+
function i(i) {
|
|
8
|
+
let { isDisabled: a, ...o } = i, s = t(o);
|
|
9
|
+
return r(() => n({
|
|
10
|
+
feature: "selection",
|
|
11
|
+
extend: (t) => {
|
|
12
|
+
let n = [], r = [];
|
|
13
|
+
t.items.forEach((e, i) => {
|
|
14
|
+
let o = t.getRowId(e, i);
|
|
15
|
+
n.push(o), a?.(e, i) || r.push(o);
|
|
16
|
+
});
|
|
17
|
+
let { all: i, some: o } = e(s.selectedItems, n, r), c = {
|
|
18
|
+
selectionMode: s.selectionMode,
|
|
19
|
+
selectedItems: s.selectedItems,
|
|
20
|
+
allRowsSelected: i,
|
|
21
|
+
someRowsSelected: o,
|
|
22
|
+
isRowSelected: s.isSelected,
|
|
23
|
+
toggleRow: s.toggle,
|
|
24
|
+
selectRow: s.select,
|
|
25
|
+
deselectRow: s.deselect,
|
|
26
|
+
toggleAllRows: (e) => s.toggleAll(r, e),
|
|
27
|
+
clearRows: s.clear
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
...t,
|
|
31
|
+
selection: c
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}), [s, a]);
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { i as useTableSelection };
|
|
38
|
+
|
|
39
|
+
//# sourceMappingURL=useTableSelection.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTableSelection.mjs","names":["useTableSelection","selectionOptions","options","selection","useSelection","useMemo","createTableFeaturePlugin","universe","selectable","id","state","item","index","isDisabled","summarizeSelection","selectionState","all","some","event"],"sources":["../../../src/components/table/useTableSelection.ts"],"sourcesContent":["import { useMemo } from 'react';\nimport { useSelection, type UseSelectionOptions } from '../../hooks/useSelection';\nimport { summarizeSelection } from '../../lib/selection';\nimport {\n createTableFeaturePlugin,\n type TableFeaturePlugin,\n type TableSelectionState,\n} from './useTableFeatures';\n\n/** Options of {@link useTableSelection}: those of `useSelection`, plus the disabled rows. */\nexport interface UseTableSelectionOptions<T> extends UseSelectionOptions {\n /**\n * Whether the selection of an item cannot change through select-all: `toggleAllRows` skips it,\n * and `allRowsSelected` does not wait for it. A disabled row keeps its state. The single-row\n * functions do not read it: disable the row's own control. Called for every item of `items`\n * whenever `useTableFeatures` computes its state: when the selection, `items`, `getRowId`,\n * `columns`, the sort or another plugin's state (the column widths) change, and when this\n * function changes. A function written inline is a new one on every render, so the state is\n * then computed, and this function called for every item, on every render (no more: no extra\n * render, and `getRows()` keeps its array); define it outside the component or memoise it to\n * call it only on those changes.\n */\n isDisabled?: (item: T, index: number) => boolean;\n}\n\n/**\n * The selection plugin of `useTableFeatures`: a set of row ids (`selectedItems`, controlled, or\n * `defaultSelectedItems`), and `state.selection` with select-all over every item of `items`,\n * rendered or not. Select-all selects every selectable row and leaves the other ids as they are;\n * deselecting all removes only the selectable ids of `items`, so a selected disabled row and ids\n * selected on another page or filtered out stay selected (`clearRows` empties the selection).\n * `onSelectedItemsChange` fires once per change with a new array of every selected id. Call it\n * unconditionally and pass the plugin to `useTableFeatures`.\n *\n * @example\n * const { selection } = useTableFeatures({ items, columns, getRowId }, [\n * useTableSelection({ selectionMode: 'multiple', onSelectedItemsChange: setSelected }),\n * ]);\n */\nexport function useTableSelection<T>(options: UseTableSelectionOptions<T>): TableFeaturePlugin<T> {\n const { isDisabled, ...selectionOptions } = options;\n const selection = useSelection(selectionOptions);\n\n return useMemo(\n () =>\n createTableFeaturePlugin<T>({\n feature: 'selection',\n extend: (state) => {\n // The universe is every row id of `items`, in their order; the selectable ids leave out\n // the disabled rows.\n const universe: string[] = [];\n const selectable: string[] = [];\n state.items.forEach((item, index) => {\n const id = state.getRowId(item, index);\n universe.push(id);\n if (!isDisabled?.(item, index)) selectable.push(id);\n });\n const { all, some } = summarizeSelection(selection.selectedItems, universe, selectable);\n const selectionState: TableSelectionState = {\n selectionMode: selection.selectionMode,\n selectedItems: selection.selectedItems,\n allRowsSelected: all,\n someRowsSelected: some,\n isRowSelected: selection.isSelected,\n toggleRow: selection.toggle,\n selectRow: selection.select,\n deselectRow: selection.deselect,\n // Over the selectable ids: all of them selected removes them (a disabled row and ids\n // outside `items` keep their state), else adds them.\n toggleAllRows: (event) => selection.toggleAll(selectable, event),\n clearRows: selection.clear,\n };\n return { ...state, selection: selectionState };\n },\n }),\n [selection, isDisabled],\n );\n}\n"],"mappings":";;;;;;AAuCA,SAAgBA,EAAqB,GAA6D;CAChG,IAAM,EAAE,eAAY,GAAGC,MAAqBC,GACtCC,IAAYC,EAAaH,CAAgB;CAE/C,OAAOI,QAEHC,EAA4B;EAC1B,SAAS;EACT,SAAS,MAAU;GAGjB,IAAMC,IAAqB,CAAC,GACtBC,IAAuB,CAAC;GAC9B,EAAM,MAAM,SAAS,GAAM,MAAU;IACnC,IAAMC,IAAKC,EAAM,SAASC,GAAMC,CAAK;IAErC,AADAL,EAAS,KAAKE,CAAE,GACXI,IAAaF,GAAMC,CAAK,KAAGJ,EAAW,KAAKC,CAAE;GACpD,CAAC;GACD,IAAM,EAAE,QAAK,YAASK,EAAmBX,EAAU,eAAeI,GAAUC,CAAU,GAChFO,IAAsC;IAC1C,eAAeZ,EAAU;IACzB,eAAeA,EAAU;IACzB,iBAAiBa;IACjB,kBAAkBC;IAClB,eAAed,EAAU;IACzB,WAAWA,EAAU;IACrB,WAAWA,EAAU;IACrB,aAAaA,EAAU;IAGvB,gBAAgB,MAAUA,EAAU,UAAUK,GAAYU,CAAK;IAC/D,WAAWf,EAAU;GACvB;GACA,OAAO;IAAE,GAAGO;IAAO,WAAWK;GAAe;EAC/C;CACF,CAAC,GACH,CAACZ,GAAWU,CAAU,CACxB;AACF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";const e=require("../../hooks/useControllable.cjs"),t=require("./useTableFeatures.cjs");let n=require("react");function r(e,t,n){let r=t.find(e=>e.columnId===n.columnId)?.compare;return r?e.slice().sort(n.direction===`descending`?(e,t)=>-r(e.item,t.item):(e,t)=>r(e.item,t.item)):e}function i(i={}){let{sort:a,defaultSort:o=null,onSortChange:s}=i,c=(0,n.useRef)(void 0),[l,u]=e.useControllable(a,o,e=>{e&&s?.(e,c.current)}),d=(0,n.useCallback)((e,t)=>{c.current=t?{event:t}:void 0;try{u(t=>{let n=e(t);return t?.columnId===n.columnId&&t.direction===n.direction?t:n})}finally{c.current=void 0}},[u]),f=(0,n.useCallback)((e,t,n)=>d(()=>({columnId:e,direction:t}),n),[d]),p=(0,n.useCallback)((e,t)=>d(t=>({columnId:e,direction:t?.columnId===e&&t.direction===`ascending`?`descending`:`ascending`}),t),[d]),m=l?.columnId,h=l?.direction,g=(0,n.useMemo)(()=>m===void 0||h===void 0?null:{columnId:m,direction:h},[m,h]),_=(0,n.useMemo)(()=>g?(e,t)=>r(e,t,g):void 0,[g]);return(0,n.useMemo)(()=>{let e={value:g,setColumnSort:f,toggleColumnSort:p,getSortDirection:e=>g?.columnId===e?g.direction:void 0};return t.createTableFeaturePlugin({feature:`sort`,sortRows:_,extend:t=>({...t,sort:e})})},[g,_,f,p])}exports.sortTableRows=r,exports.useTableSort=i;
|
|
2
|
+
//# sourceMappingURL=useTableSort.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTableSort.cjs","names":["useRef","useControllable","useCallback","useMemo","createTableFeaturePlugin","require_useControllable","require_useTableFeatures","react","sortTableRows","compare","columns","column","sort","rows","a","b","useTableSort","sortProp","options","detailsRef","current","setSort","defaultSort","next","onSortChange","change","event","update","previous","setColumnSort","toggleColumnSort","columnId","direction","value","sortRows","sortState","id","state"],"sources":["../../../src/components/table/useTableSort.ts"],"sourcesContent":["import { useCallback, useMemo, useRef } from 'react';\nimport { useControllable } from '../../hooks/useControllable';\nimport type {\n DataGridColumn,\n DataGridSort,\n DataGridSortChangeDetails,\n SortDirection,\n} from './DataGrid';\nimport {\n createTableFeaturePlugin,\n type TableFeaturePlugin,\n type TableRowData,\n type TableSortState,\n} from './useTableFeatures';\n\n/** Options of {@link useTableSort}. */\nexport interface UseTableSortOptions {\n /**\n * The sort (controlled): the sorted column and its direction, or `null` for the order of\n * `items`. Pair it with `onSortChange`.\n */\n sort?: DataGridSort | null;\n /**\n * The sort at first render (uncontrolled).\n * @default null\n */\n defaultSort?: DataGridSort | null;\n /**\n * Called with the new sort whenever it changes, and the event behind it when the change was\n * given one. Setting the sort it already has does not call it.\n */\n onSortChange?: (sort: DataGridSort, details?: DataGridSortChangeDetails) => void;\n}\n\n/**\n * The rows in the order of `sort`: a stable sort of a copy by the `compare` of the sorted column,\n * looked up once (not per comparison). Descending negates `compare`, so rows that compare equal\n * keep their order in both directions. Without a column of that id, or without `compare`, the\n * rows are returned as they are.\n */\nexport function sortTableRows<T, R extends { item: T }>(\n rows: readonly R[],\n columns: readonly DataGridColumn<T>[],\n sort: DataGridSort,\n): readonly R[] {\n const compare = columns.find((column) => column.columnId === sort.columnId)?.compare;\n if (!compare) return rows;\n return rows\n .slice()\n .sort(\n sort.direction === 'descending'\n ? (a, b) => -compare(a.item, b.item)\n : (a, b) => compare(a.item, b.item),\n );\n}\n\n/**\n * The sort plugin of `useTableFeatures`: sorts the rows by the `compare` of a column and fills\n * `state.sort`. The sort is controlled with `sort` or uncontrolled with `defaultSort`;\n * `toggleColumnSort` goes from ascending to descending as a sortable DataGrid header does. The\n * sorted column is the one whose `columnId` is the sort's `columnId` (the deprecated `id` of a\n * column is not read). Call it unconditionally and pass the plugin to `useTableFeatures`.\n *\n * @example\n * const state = useTableFeatures({ items, columns }, [\n * useTableSort({ defaultSort: { columnId: 'name', direction: 'ascending' } }),\n * ]);\n * state.sort.toggleColumnSort('name');\n */\nexport function useTableSort<T>(options: UseTableSortOptions = {}): TableFeaturePlugin<T> {\n const { sort: sortProp, defaultSort = null, onSortChange } = options;\n\n // The details of the change being made, set around the setter: useControllable calls its\n // onChange synchronously inside it, and only on change.\n const detailsRef = useRef<DataGridSortChangeDetails | undefined>(undefined);\n const [current, setSort] = useControllable<DataGridSort | null>(sortProp, defaultSort, (next) => {\n if (next) onSortChange?.(next, detailsRef.current);\n });\n\n const change = useCallback(\n (update: (previous: DataGridSort | null) => DataGridSort, event: Event | undefined) => {\n detailsRef.current = event ? { event } : undefined;\n try {\n setSort((previous) => {\n const next = update(previous);\n return previous?.columnId === next.columnId && previous.direction === next.direction\n ? previous\n : next;\n });\n } finally {\n detailsRef.current = undefined;\n }\n },\n [setSort],\n );\n\n const setColumnSort = useCallback<TableSortState['setColumnSort']>(\n (columnId, direction, event) => change(() => ({ columnId, direction }), event),\n [change],\n );\n\n const toggleColumnSort = useCallback<TableSortState['toggleColumnSort']>(\n (columnId, event) =>\n change(\n (previous) => ({\n columnId,\n direction:\n previous?.columnId === columnId && previous.direction === 'ascending'\n ? 'descending'\n : 'ascending',\n }),\n event,\n ),\n [change],\n );\n\n // Kept by content: a controlled sort passed as a new object with the same value keeps the plugin,\n // and with it the sorted rows.\n const columnId = current?.columnId;\n const direction: SortDirection | undefined = current?.direction;\n const value = useMemo<DataGridSort | null>(\n () => (columnId === undefined || direction === undefined ? null : { columnId, direction }),\n [columnId, direction],\n );\n\n // The row order depends on the sort alone: useTableFeatures rebuilds the rows when it changes.\n const sortRows = useMemo(\n () =>\n value\n ? (rows: readonly TableRowData<T>[], columns: readonly DataGridColumn<T>[]) =>\n sortTableRows(rows, columns, value)\n : undefined,\n [value],\n );\n\n return useMemo(() => {\n const sortState: TableSortState = {\n value,\n setColumnSort,\n toggleColumnSort,\n getSortDirection: (id) => (value?.columnId === id ? value.direction : undefined),\n };\n return createTableFeaturePlugin<T>({\n feature: 'sort',\n sortRows,\n extend: (state) => ({ ...state, sort: sortState }),\n });\n }, [value, sortRows, setColumnSort, toggleColumnSort]);\n}\n"],"mappings":"2HAwCA,SAAgBQ,EACd,EACA,EACA,EACc,CACd,IAAMC,EAAUC,EAAQ,KAAM,GAAWC,EAAO,WAAaC,EAAK,QAAQ,CAAC,EAAE,QAE7E,OADKH,EACEI,EACJ,MAAM,CAAC,CACP,KACCD,EAAK,YAAc,cACd,EAAG,IAAM,CAACH,EAAQK,EAAE,KAAMC,EAAE,IAAI,GAChC,EAAG,IAAMN,EAAQK,EAAE,KAAMC,EAAE,IAAI,CACtC,EAPmBF,CAQvB,CAeA,SAAgBG,EAAgB,EAA+B,CAAC,EAA0B,CACxF,GAAM,CAAE,KAAMC,EAAU,cAAc,KAAM,gBAAiBC,EAIvDC,GAAA,EAAanB,EAAAA,OAAAA,CAA8C,IAAA,EAAS,EACpE,CAACoB,EAASC,GAAWpB,EAAAA,gBAAqCgB,EAAUK,EAAc,GAAS,CAC3FC,GAAMC,IAAeD,EAAMJ,EAAW,OAAO,CACnD,CAAC,EAEKM,GAAA,EAASvB,EAAAA,YAAAA,EACZ,EAAyD,IAA6B,CACrF,EAAW,QAAUwB,EAAQ,CAAE,OAAM,EAAI,IAAA,GACzC,GAAI,CACFL,EAAS,GAAa,CACpB,IAAME,EAAOI,EAAOC,CAAQ,EAC5B,OAAOA,GAAU,WAAaL,EAAK,UAAYK,EAAS,YAAcL,EAAK,UACvEK,EACAL,CACN,CAAC,CACH,QAAU,CACR,EAAW,QAAU,IAAA,EACvB,CACF,EACA,CAACF,CAAO,CACV,EAEMQ,GAAA,EAAgB3B,EAAAA,YAAAA,EACnB,EAAU,EAAW,IAAUuB,OAAc,CAAE,WAAU,WAAU,GAAIC,CAAK,EAC7E,CAACD,CAAM,CACT,EAEMK,GAAA,EAAmB5B,EAAAA,YAAAA,EACtB,EAAU,IACTuB,EACG,IAAc,CACb,WACA,UACEG,GAAU,WAAaG,GAAYH,EAAS,YAAc,YACtD,aACA,WACR,GACAF,CACF,EACF,CAACD,CAAM,CACT,EAIMM,EAAWX,GAAS,SACpBY,EAAuCZ,GAAS,UAChDa,GAAA,EAAQ9B,EAAAA,QAAAA,KACL4B,IAAa,IAAA,IAAaC,IAAc,IAAA,GAAY,KAAO,CAAE,WAAU,WAAU,EACxF,CAACD,EAAUC,CAAS,CACtB,EAGME,GAAA,EAAW/B,EAAAA,QAAAA,KAEb8B,GACK,EAAkC,IACjCzB,EAAcK,EAAMH,EAASuB,CAAK,EACpC,IAAA,GACN,CAACA,CAAK,CACR,EAEA,OAAA,EAAO9B,EAAAA,QAAAA,KAAc,CACnB,IAAMgC,EAA4B,CAChC,QACA,gBACA,mBACA,iBAAmB,GAAQF,GAAO,WAAaG,EAAKH,EAAM,UAAY,IAAA,EACxE,EACA,OAAO7B,EAAAA,yBAA4B,CACjC,QAAS,OACT,WACA,OAAS,IAAW,CAAE,GAAGiC,EAAO,KAAMF,CAAU,EAClD,CAAC,CACH,EAAG,CAACF,EAAOC,EAAUL,EAAeC,CAAgB,CAAC,CACvD"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useControllable as e } from "../../hooks/useControllable.mjs";
|
|
3
|
+
import { createTableFeaturePlugin as t } from "./useTableFeatures.mjs";
|
|
4
|
+
import { useCallback as n, useMemo as r, useRef as i } from "react";
|
|
5
|
+
//#region src/components/table/useTableSort.ts
|
|
6
|
+
function a(e, t, n) {
|
|
7
|
+
let r = t.find((e) => e.columnId === n.columnId)?.compare;
|
|
8
|
+
return r ? e.slice().sort(n.direction === "descending" ? (e, t) => -r(e.item, t.item) : (e, t) => r(e.item, t.item)) : e;
|
|
9
|
+
}
|
|
10
|
+
function o(o = {}) {
|
|
11
|
+
let { sort: s, defaultSort: c = null, onSortChange: l } = o, u = i(void 0), [d, f] = e(s, c, (e) => {
|
|
12
|
+
e && l?.(e, u.current);
|
|
13
|
+
}), p = n((e, t) => {
|
|
14
|
+
u.current = t ? { event: t } : void 0;
|
|
15
|
+
try {
|
|
16
|
+
f((t) => {
|
|
17
|
+
let n = e(t);
|
|
18
|
+
return t?.columnId === n.columnId && t.direction === n.direction ? t : n;
|
|
19
|
+
});
|
|
20
|
+
} finally {
|
|
21
|
+
u.current = void 0;
|
|
22
|
+
}
|
|
23
|
+
}, [f]), m = n((e, t, n) => p(() => ({
|
|
24
|
+
columnId: e,
|
|
25
|
+
direction: t
|
|
26
|
+
}), n), [p]), h = n((e, t) => p((t) => ({
|
|
27
|
+
columnId: e,
|
|
28
|
+
direction: t?.columnId === e && t.direction === "ascending" ? "descending" : "ascending"
|
|
29
|
+
}), t), [p]), g = d?.columnId, _ = d?.direction, v = r(() => g === void 0 || _ === void 0 ? null : {
|
|
30
|
+
columnId: g,
|
|
31
|
+
direction: _
|
|
32
|
+
}, [g, _]), y = r(() => v ? (e, t) => a(e, t, v) : void 0, [v]);
|
|
33
|
+
return r(() => {
|
|
34
|
+
let e = {
|
|
35
|
+
value: v,
|
|
36
|
+
setColumnSort: m,
|
|
37
|
+
toggleColumnSort: h,
|
|
38
|
+
getSortDirection: (e) => v?.columnId === e ? v.direction : void 0
|
|
39
|
+
};
|
|
40
|
+
return t({
|
|
41
|
+
feature: "sort",
|
|
42
|
+
sortRows: y,
|
|
43
|
+
extend: (t) => ({
|
|
44
|
+
...t,
|
|
45
|
+
sort: e
|
|
46
|
+
})
|
|
47
|
+
});
|
|
48
|
+
}, [
|
|
49
|
+
v,
|
|
50
|
+
y,
|
|
51
|
+
m,
|
|
52
|
+
h
|
|
53
|
+
]);
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { a as sortTableRows, o as useTableSort };
|
|
57
|
+
|
|
58
|
+
//# sourceMappingURL=useTableSort.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTableSort.mjs","names":["sortTableRows","compare","columns","column","sort","rows","a","b","useTableSort","sortProp","options","detailsRef","useRef","current","setSort","useControllable","defaultSort","next","onSortChange","change","useCallback","event","update","previous","setColumnSort","toggleColumnSort","columnId","direction","value","useMemo","sortRows","sortState","id","createTableFeaturePlugin","state"],"sources":["../../../src/components/table/useTableSort.ts"],"sourcesContent":["import { useCallback, useMemo, useRef } from 'react';\nimport { useControllable } from '../../hooks/useControllable';\nimport type {\n DataGridColumn,\n DataGridSort,\n DataGridSortChangeDetails,\n SortDirection,\n} from './DataGrid';\nimport {\n createTableFeaturePlugin,\n type TableFeaturePlugin,\n type TableRowData,\n type TableSortState,\n} from './useTableFeatures';\n\n/** Options of {@link useTableSort}. */\nexport interface UseTableSortOptions {\n /**\n * The sort (controlled): the sorted column and its direction, or `null` for the order of\n * `items`. Pair it with `onSortChange`.\n */\n sort?: DataGridSort | null;\n /**\n * The sort at first render (uncontrolled).\n * @default null\n */\n defaultSort?: DataGridSort | null;\n /**\n * Called with the new sort whenever it changes, and the event behind it when the change was\n * given one. Setting the sort it already has does not call it.\n */\n onSortChange?: (sort: DataGridSort, details?: DataGridSortChangeDetails) => void;\n}\n\n/**\n * The rows in the order of `sort`: a stable sort of a copy by the `compare` of the sorted column,\n * looked up once (not per comparison). Descending negates `compare`, so rows that compare equal\n * keep their order in both directions. Without a column of that id, or without `compare`, the\n * rows are returned as they are.\n */\nexport function sortTableRows<T, R extends { item: T }>(\n rows: readonly R[],\n columns: readonly DataGridColumn<T>[],\n sort: DataGridSort,\n): readonly R[] {\n const compare = columns.find((column) => column.columnId === sort.columnId)?.compare;\n if (!compare) return rows;\n return rows\n .slice()\n .sort(\n sort.direction === 'descending'\n ? (a, b) => -compare(a.item, b.item)\n : (a, b) => compare(a.item, b.item),\n );\n}\n\n/**\n * The sort plugin of `useTableFeatures`: sorts the rows by the `compare` of a column and fills\n * `state.sort`. The sort is controlled with `sort` or uncontrolled with `defaultSort`;\n * `toggleColumnSort` goes from ascending to descending as a sortable DataGrid header does. The\n * sorted column is the one whose `columnId` is the sort's `columnId` (the deprecated `id` of a\n * column is not read). Call it unconditionally and pass the plugin to `useTableFeatures`.\n *\n * @example\n * const state = useTableFeatures({ items, columns }, [\n * useTableSort({ defaultSort: { columnId: 'name', direction: 'ascending' } }),\n * ]);\n * state.sort.toggleColumnSort('name');\n */\nexport function useTableSort<T>(options: UseTableSortOptions = {}): TableFeaturePlugin<T> {\n const { sort: sortProp, defaultSort = null, onSortChange } = options;\n\n // The details of the change being made, set around the setter: useControllable calls its\n // onChange synchronously inside it, and only on change.\n const detailsRef = useRef<DataGridSortChangeDetails | undefined>(undefined);\n const [current, setSort] = useControllable<DataGridSort | null>(sortProp, defaultSort, (next) => {\n if (next) onSortChange?.(next, detailsRef.current);\n });\n\n const change = useCallback(\n (update: (previous: DataGridSort | null) => DataGridSort, event: Event | undefined) => {\n detailsRef.current = event ? { event } : undefined;\n try {\n setSort((previous) => {\n const next = update(previous);\n return previous?.columnId === next.columnId && previous.direction === next.direction\n ? previous\n : next;\n });\n } finally {\n detailsRef.current = undefined;\n }\n },\n [setSort],\n );\n\n const setColumnSort = useCallback<TableSortState['setColumnSort']>(\n (columnId, direction, event) => change(() => ({ columnId, direction }), event),\n [change],\n );\n\n const toggleColumnSort = useCallback<TableSortState['toggleColumnSort']>(\n (columnId, event) =>\n change(\n (previous) => ({\n columnId,\n direction:\n previous?.columnId === columnId && previous.direction === 'ascending'\n ? 'descending'\n : 'ascending',\n }),\n event,\n ),\n [change],\n );\n\n // Kept by content: a controlled sort passed as a new object with the same value keeps the plugin,\n // and with it the sorted rows.\n const columnId = current?.columnId;\n const direction: SortDirection | undefined = current?.direction;\n const value = useMemo<DataGridSort | null>(\n () => (columnId === undefined || direction === undefined ? null : { columnId, direction }),\n [columnId, direction],\n );\n\n // The row order depends on the sort alone: useTableFeatures rebuilds the rows when it changes.\n const sortRows = useMemo(\n () =>\n value\n ? (rows: readonly TableRowData<T>[], columns: readonly DataGridColumn<T>[]) =>\n sortTableRows(rows, columns, value)\n : undefined,\n [value],\n );\n\n return useMemo(() => {\n const sortState: TableSortState = {\n value,\n setColumnSort,\n toggleColumnSort,\n getSortDirection: (id) => (value?.columnId === id ? value.direction : undefined),\n };\n return createTableFeaturePlugin<T>({\n feature: 'sort',\n sortRows,\n extend: (state) => ({ ...state, sort: sortState }),\n });\n }, [value, sortRows, setColumnSort, toggleColumnSort]);\n}\n"],"mappings":";;;;;AAwCA,SAAgBA,EACd,GACA,GACA,GACc;CACd,IAAMC,IAAUC,EAAQ,MAAM,MAAWC,EAAO,aAAaC,EAAK,QAAQ,CAAC,EAAE;CAE7E,OADKH,IACEI,EACJ,MAAM,CAAC,CACP,KACCD,EAAK,cAAc,gBACd,GAAG,MAAM,CAACH,EAAQK,EAAE,MAAMC,EAAE,IAAI,KAChC,GAAG,MAAMN,EAAQK,EAAE,MAAMC,EAAE,IAAI,CACtC,IAPmBF;AAQvB;AAeA,SAAgBG,EAAgB,IAA+B,CAAC,GAA0B;CACxF,IAAM,EAAE,MAAMC,GAAU,iBAAc,MAAM,oBAAiBC,GAIvDC,IAAaC,EAA8C,KAAA,CAAS,GACpE,CAACC,GAASC,KAAWC,EAAqCN,GAAUO,IAAc,MAAS;EAC/F,AAAIC,KAAMC,IAAeD,GAAMN,EAAW,OAAO;CACnD,CAAC,GAEKQ,IAASC,GACZ,GAAyD,MAA6B;EACrF,EAAW,UAAUC,IAAQ,EAAE,SAAM,IAAI,KAAA;EACzC,IAAI;GACF,GAAS,MAAa;IACpB,IAAMJ,IAAOK,EAAOC,CAAQ;IAC5B,OAAOA,GAAU,aAAaN,EAAK,YAAYM,EAAS,cAAcN,EAAK,YACvEM,IACAN;GACN,CAAC;EACH,UAAU;GACR,EAAW,UAAU,KAAA;EACvB;CACF,GACA,CAACH,CAAO,CACV,GAEMU,IAAgBJ,GACnB,GAAU,GAAW,MAAUD,SAAc;EAAE;EAAU;CAAU,IAAIE,CAAK,GAC7E,CAACF,CAAM,CACT,GAEMM,IAAmBL,GACtB,GAAU,MACTD,GACG,OAAc;EACb;EACA,WACEI,GAAU,aAAaG,KAAYH,EAAS,cAAc,cACtD,eACA;CACR,IACAF,CACF,GACF,CAACF,CAAM,CACT,GAIMO,IAAWb,GAAS,UACpBc,IAAuCd,GAAS,WAChDe,IAAQC,QACLH,MAAa,KAAA,KAAaC,MAAc,KAAA,IAAY,OAAO;EAAE;EAAU;CAAU,GACxF,CAACD,GAAUC,CAAS,CACtB,GAGMG,IAAWD,QAEbD,KACK,GAAkC,MACjC5B,EAAcK,GAAMH,GAAS0B,CAAK,IACpC,KAAA,GACN,CAACA,CAAK,CACR;CAEA,OAAOC,QAAc;EACnB,IAAME,IAA4B;GAChC;GACA;GACA;GACA,mBAAmB,MAAQH,GAAO,aAAaI,IAAKJ,EAAM,YAAY,KAAA;EACxE;EACA,OAAOK,EAA4B;GACjC,SAAS;GACT;GACA,SAAS,OAAW;IAAE,GAAGC;IAAO,MAAMH;GAAU;EAClD,CAAC;CACH,GAAG;EAACH;EAAOE;EAAUN;EAAeC;CAAgB,CAAC;AACvD"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use client";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./useEventCallback.cjs");let n=require("react");n=e.__toESM(n,1);function r(e,t){return e===t||e.length===t.length&&e.every((e,n)=>e===t[n])}function i(e,t,n,r){let i=e.length;if(i===0)return null;let a=t===null?-1:e.indexOf(t);if(a===-1)return n>0?e[0]:e[i-1];let o=a+n;return(o<0||o>=i)&&(o=r&&Math.abs(n)===1?(o+i)%i:Math.max(0,Math.min(i-1,o))),e[o]}function a(e){let{items:a,getId:o,enabled:s=!0,fallback:c=null,loop:l=!1,activateFirstOnChange:u=!1,getElement:d,onActiveValueChange:
|
|
1
|
+
"use client";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./useEventCallback.cjs");let n=require("react");n=e.__toESM(n,1);function r(e,t){return e===t||e.length===t.length&&e.every((e,n)=>e===t[n])}function i(e,t,n,r){let i=e.length;if(i===0)return null;let a=t===null?-1:e.indexOf(t);if(a===-1)return n>0?e[0]:e[i-1];let o=a+n;return(o<0||o>=i)&&(o=r&&Math.abs(n)===1?(o+i)%i:Math.max(0,Math.min(i-1,o))),e[o]}function a(e){let{items:a,getId:o,enabled:s=!0,fallback:c=null,loop:l=!1,activateFirstOnChange:u=!1,getElement:d,onScrollToItem:f,onActiveValueChange:p}=e,m=n.useMemo(()=>new Set(a),[a]),[h,g]=n.useState(null),_=s?h:null,[v,y]=n.useState(()=>({enabled:s,items:a}));if(u){let e=!r(v.items,a);(e||v.enabled!==s)&&(y({enabled:s,items:a}),s&&e&&(_=a[0]??null))}_!==null&&!m.has(_)&&(_=null),_!==h&&g(_);let b=s?_??(c!==null&&m.has(c)?c:null):null,x=b===null?void 0:o(b),S=n.useRef(null),C=t.useEventCallback((e,t)=>{S.current=t?.scroll===!1?e:null,g(e)}),w=t.useEventCallback(e=>{s&&m.has(e)&&e!==b&&(S.current=e,g(e))}),T=t.useEventCallback(()=>C(a[0]??null)),E=t.useEventCallback(()=>C(a[a.length-1]??null)),D=t.useEventCallback(()=>C(i(a,b,1,l))),O=t.useEventCallback(()=>C(i(a,b,-1,l))),k=t.useEventCallback(e=>C(i(a,b,e,!1))),A=t.useEventCallback(e=>d?.(e)||(typeof document>`u`?null:document.getElementById(o(e)))),j=t.useEventCallback(e=>{if(!f)return;let t=a.indexOf(e);t!==-1&&f(t)});n.useLayoutEffect(()=>{let e=b!==null&&b===S.current;S.current=null,!(b===null||e)&&(j(b),A(b)?.scrollIntoView?.({block:`nearest`}))},[b,A,j]);let M=n.useRef(null),N=t.useEventCallback(e=>p?.(e));return n.useEffect(()=>{M.current!==b&&(M.current=b,N(b))},[b,N]),{activeValue:b,activeDescendantId:x,setActiveValue:C,highlight:w,first:T,last:E,next:D,prev:O,move:k}}exports.useActiveDescendant=a;
|
|
2
2
|
//# sourceMappingURL=useActiveDescendant.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useActiveDescendant.cjs","names":["React","useEventCallback","require_runtime","require_useEventCallback","react","sameValues","a","b","value","index","step","count","values","current","delta","next","loop","useActiveDescendant","options","itemSet","items","movedRaw","setMovedRaw","moved","enabled","track","setTrack","activateFirstOnChange","itemsChanged","activeValue","fallback","activeDescendantId","getId","noScrollRef","setActiveValue","moveOptions","highlight","first","last","prev","move","resolveElement","getElement","suppressed","reportedRef","report","onActiveValueChange"],"sources":["../../src/hooks/useActiveDescendant.ts"],"sourcesContent":["import * as React from 'react';\nimport { useEventCallback } from './useEventCallback';\n\n/** Options of {@link useActiveDescendant}. */\nexport interface UseActiveDescendantOptions {\n /** The values that can become active, in navigation order. */\n items: readonly string[];\n /** The DOM id of an item's element: the `aria-activedescendant` target. */\n getId: (value: string) => string;\n /**\n * While `false`, no item is active and the moved-to item is forgotten (a closed popup, an\n * unfocused list). A move made in the same update that enables the hook is kept.\n * @default true\n */\n enabled?: boolean;\n /**\n * The active item while none was moved to, or after the moved-to item left `items` (the\n * selected option, for example). Ignored when it is not in `items`.\n * @default null\n */\n fallback?: string | null;\n /** `next()` and `prev()` wrap at the ends. @default false */\n loop?: boolean;\n /**\n * Whenever `items` changes while the hook is enabled (compared by content), its first item\n * becomes active — also when the change comes with enabling (the keystroke that opens a\n * filtered list). Enabling with unchanged items keeps the fallback. It wins over a move made in\n * the same update.\n * @default false\n */\n activateFirstOnChange?: boolean;\n /** The element of an item, for scrolling it into view. @default document.getElementById(getId(value)) */\n getElement?: (value: string) => HTMLElement | null;\n /** Called after the active item changed (from an effect), `null` included. */\n onActiveValueChange?: (value: string | null) => void;\n}\n\n/** Result of {@link useActiveDescendant}. The methods have stable identities. */\nexport interface UseActiveDescendantResult {\n /** The moved-to item, else the fallback; `null` while disabled. Derived during render. */\n activeValue: string | null;\n /** `getId(activeValue)`, or `undefined`: spread as `aria-activedescendant`. */\n activeDescendantId: string | undefined;\n /**\n * Moves to `value`, or back to the fallback with `null`. The value is kept as it is; the next\n * render drops it when it is not in that render's `items` or the hook is disabled, clearing any\n * earlier move. Scrolled into view unless `scroll` is `false`.\n */\n setActiveValue: (value: string | null, options?: { scroll?: boolean }) => void;\n /**\n * Moves to `value` for pointer movement, not scrolled into view; ignored while disabled, when\n * `value` is not in `items` or is already active.\n */\n highlight: (value: string) => void;\n /** Moves to the first item. */\n first: () => void;\n /** Moves to the last item. */\n last: () => void;\n /** The item after the active one (the first when none is active); wraps with `loop`. */\n next: () => void;\n /** The item before the active one (the last when none is active); wraps with `loop`. */\n prev: () => void;\n /**\n * Moves `delta` items (PageDown: `move(10)`), clamped at the ends, never wrapping; from no active\n * item, a positive delta goes to the first item and a negative one to the last.\n */\n move: (delta: number) => void;\n}\n\nfunction sameValues(a: readonly string[], b: readonly string[]): boolean {\n if (a === b) return true;\n if (a.length !== b.length) return false;\n return a.every((value, index) => value === b[index]);\n}\n\nfunction step(values: readonly string[], current: string | null, delta: number, loop: boolean) {\n const count = values.length;\n if (count === 0) return null;\n const index = current === null ? -1 : values.indexOf(current);\n if (index === -1) return delta > 0 ? values[0] : values[count - 1];\n let next = index + delta;\n if (next < 0 || next >= count) {\n next =\n loop && Math.abs(delta) === 1\n ? (next + count) % count\n : Math.max(0, Math.min(count - 1, next));\n }\n return values[next];\n}\n\n/**\n * The active (virtually focused) item of a composite that keeps DOM focus on one element and points\n * `aria-activedescendant` at the active item: a combobox, a listbox that holds focus, a command\n * palette. The state is React state, so the active id is derived during render (server HTML\n * included) and nothing reads the DOM during render.\n *\n * - The moved-to item is dropped when it leaves `items` or while `enabled` is `false`, and does not\n * come back without a user action; `fallback` is active while nothing was moved to.\n * - Keyboard and programmatic moves are scrolled into view (`{ block: 'nearest' }`); pointer moves\n * (`highlight`) and `setActiveValue(value, { scroll: false })` are not.\n * - `onActiveValueChange` reports every change after the commit, once (StrictMode included).\n *\n * `useListbox` is built on it; use it directly for composites that are not listboxes.\n *\n * @example\n * const ad = useActiveDescendant({ items: results.map((r) => r.id), getId: (id) => `cmd-${id}`, enabled: open });\n * <input aria-activedescendant={ad.activeDescendantId} onKeyDown={(e) => { if (e.key === 'ArrowDown') ad.next(); }} />\n */\nexport function useActiveDescendant(\n options: UseActiveDescendantOptions,\n): UseActiveDescendantResult {\n const {\n items,\n getId,\n enabled = true,\n fallback = null,\n loop = false,\n activateFirstOnChange = false,\n getElement,\n onActiveValueChange,\n } = options;\n\n const itemSet = React.useMemo(() => new Set(items), [items]);\n const [movedRaw, setMovedRaw] = React.useState<string | null>(null);\n let moved = enabled ? movedRaw : null;\n\n // activateFirstOnChange: tracked (and updated) only while the option is on, so a render without\n // it adds no render-phase update (C-HOOKS: adjust-during-render).\n const [track, setTrack] = React.useState(() => ({ enabled, items }));\n if (activateFirstOnChange) {\n const itemsChanged = !sameValues(track.items, items);\n if (itemsChanged || track.enabled !== enabled) {\n setTrack({ enabled, items });\n if (enabled && itemsChanged) moved = items[0] ?? null;\n }\n }\n if (moved !== null && !itemSet.has(moved)) moved = null;\n if (moved !== movedRaw) setMovedRaw(moved);\n\n const activeValue = enabled\n ? (moved ?? (fallback !== null && itemSet.has(fallback) ? fallback : null))\n : null;\n const activeDescendantId = activeValue === null ? undefined : getId(activeValue);\n\n // The value the pointer (or a `scroll: false` move) activated, until the scroll effect saw it.\n const noScrollRef = React.useRef<string | null>(null);\n\n const setActiveValue = useEventCallback(\n (value: string | null, moveOptions?: { scroll?: boolean }) => {\n noScrollRef.current = moveOptions?.scroll === false ? value : null;\n setMovedRaw(value);\n },\n );\n const highlight = useEventCallback((value: string) => {\n if (!enabled || !itemSet.has(value) || value === activeValue) return;\n noScrollRef.current = value;\n setMovedRaw(value);\n });\n const first = useEventCallback(() => setActiveValue(items[0] ?? null));\n const last = useEventCallback(() => setActiveValue(items[items.length - 1] ?? null));\n const next = useEventCallback(() => setActiveValue(step(items, activeValue, 1, loop)));\n const prev = useEventCallback(() => setActiveValue(step(items, activeValue, -1, loop)));\n // Never wraps, whatever `loop` is: `loop` is next()/prev()'s wrap at the ends only (fix round 1).\n const move = useEventCallback((delta: number) =>\n setActiveValue(step(items, activeValue, delta, false)),\n );\n\n const resolveElement = useEventCallback((value: string): HTMLElement | null => {\n const custom = getElement?.(value);\n if (custom) return custom;\n return typeof document === 'undefined' ? null : document.getElementById(getId(value));\n });\n React.useLayoutEffect(() => {\n const suppressed = activeValue !== null && activeValue === noScrollRef.current;\n noScrollRef.current = null;\n if (activeValue === null || suppressed) return;\n resolveElement(activeValue)?.scrollIntoView?.({ block: 'nearest' });\n }, [activeValue, resolveElement]);\n\n const reportedRef = React.useRef<string | null>(null);\n const report = useEventCallback((value: string | null) => onActiveValueChange?.(value));\n React.useEffect(() => {\n if (reportedRef.current === activeValue) return;\n reportedRef.current = activeValue;\n report(activeValue);\n }, [activeValue, report]);\n\n return {\n activeValue,\n activeDescendantId,\n setActiveValue,\n highlight,\n first,\n last,\n next,\n prev,\n move,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useActiveDescendant.cjs","names":["React","useEventCallback","require_runtime","require_useEventCallback","react","sameValues","a","b","value","index","step","count","values","current","delta","next","loop","useActiveDescendant","options","itemSet","items","movedRaw","setMovedRaw","moved","enabled","track","setTrack","activateFirstOnChange","itemsChanged","activeValue","fallback","activeDescendantId","getId","noScrollRef","setActiveValue","moveOptions","highlight","first","last","prev","move","resolveElement","getElement","scrollToItem","onScrollToItem","suppressed","reportedRef","report","onActiveValueChange"],"sources":["../../src/hooks/useActiveDescendant.ts"],"sourcesContent":["import * as React from 'react';\nimport { useEventCallback } from './useEventCallback';\n\n/** Options of {@link useActiveDescendant}. */\nexport interface UseActiveDescendantOptions {\n /** The values that can become active, in navigation order. */\n items: readonly string[];\n /** The DOM id of an item's element: the `aria-activedescendant` target. */\n getId: (value: string) => string;\n /**\n * While `false`, no item is active and the moved-to item is forgotten (a closed popup, an\n * unfocused list). A move made in the same update that enables the hook is kept.\n * @default true\n */\n enabled?: boolean;\n /**\n * The active item while none was moved to, or after the moved-to item left `items` (the\n * selected option, for example). Ignored when it is not in `items`.\n * @default null\n */\n fallback?: string | null;\n /** `next()` and `prev()` wrap at the ends. @default false */\n loop?: boolean;\n /**\n * Whenever `items` changes while the hook is enabled (compared by content), its first item\n * becomes active — also when the change comes with enabling (the keystroke that opens a\n * filtered list). Enabling with unchanged items keeps the fallback. It wins over a move made in\n * the same update.\n * @default false\n */\n activateFirstOnChange?: boolean;\n /** The element of an item, for scrolling it into view. @default document.getElementById(getId(value)) */\n getElement?: (value: string) => HTMLElement | null;\n /**\n * Called for every move that scrolls the active item into view, for a windowed list that renders\n * part of its items: `index` is the item's index in `items`. Scroll the list so that it shows and\n * renders the item (a virtual window's `scrollToIndex`, which moves the window in the same\n * update); an element, when there is one (`getElement`, then the id lookup), is then scrolled\n * into view as well. `aria-activedescendant` already names the item, and points at its element\n * once it mounts. Called after the same moves that scroll (not after a pointer move or a\n * `scroll: false` move), from a layout effect, once per change of the active item. Without it,\n * an item without an element is not scrolled to.\n */\n onScrollToItem?: (index: number) => void;\n /** Called after the active item changed (from an effect), `null` included. */\n onActiveValueChange?: (value: string | null) => void;\n}\n\n/** Result of {@link useActiveDescendant}. The methods have stable identities. */\nexport interface UseActiveDescendantResult {\n /** The moved-to item, else the fallback; `null` while disabled. Derived during render. */\n activeValue: string | null;\n /** `getId(activeValue)`, or `undefined`: spread as `aria-activedescendant`. */\n activeDescendantId: string | undefined;\n /**\n * Moves to `value`, or back to the fallback with `null`. The value is kept as it is; the next\n * render drops it when it is not in that render's `items` or the hook is disabled, clearing any\n * earlier move. Scrolled into view unless `scroll` is `false`.\n */\n setActiveValue: (value: string | null, options?: { scroll?: boolean }) => void;\n /**\n * Moves to `value` for pointer movement, not scrolled into view; ignored while disabled, when\n * `value` is not in `items` or is already active.\n */\n highlight: (value: string) => void;\n /** Moves to the first item. */\n first: () => void;\n /** Moves to the last item. */\n last: () => void;\n /** The item after the active one (the first when none is active); wraps with `loop`. */\n next: () => void;\n /** The item before the active one (the last when none is active); wraps with `loop`. */\n prev: () => void;\n /**\n * Moves `delta` items (PageDown: `move(10)`), clamped at the ends, never wrapping; from no active\n * item, a positive delta goes to the first item and a negative one to the last.\n */\n move: (delta: number) => void;\n}\n\nfunction sameValues(a: readonly string[], b: readonly string[]): boolean {\n if (a === b) return true;\n if (a.length !== b.length) return false;\n return a.every((value, index) => value === b[index]);\n}\n\nfunction step(values: readonly string[], current: string | null, delta: number, loop: boolean) {\n const count = values.length;\n if (count === 0) return null;\n const index = current === null ? -1 : values.indexOf(current);\n if (index === -1) return delta > 0 ? values[0] : values[count - 1];\n let next = index + delta;\n if (next < 0 || next >= count) {\n next =\n loop && Math.abs(delta) === 1\n ? (next + count) % count\n : Math.max(0, Math.min(count - 1, next));\n }\n return values[next];\n}\n\n/**\n * The active (virtually focused) item of a composite that keeps DOM focus on one element and points\n * `aria-activedescendant` at the active item: a combobox, a listbox that holds focus, a command\n * palette. The state is React state, so the active id is derived during render (server HTML\n * included) and nothing reads the DOM during render.\n *\n * - The moved-to item is dropped when it leaves `items` or while `enabled` is `false`, and does not\n * come back without a user action; `fallback` is active while nothing was moved to.\n * - Keyboard and programmatic moves are scrolled into view (`{ block: 'nearest' }`); pointer moves\n * (`highlight`) and `setActiveValue(value, { scroll: false })` are not. With `onScrollToItem` (a\n * windowed list) each such move is first passed to it by the item's index, also for an item\n * without an element.\n * - `onActiveValueChange` reports every change after the commit, once (StrictMode included).\n *\n * `useListbox` is built on it; use it directly for composites that are not listboxes.\n *\n * @example\n * const ad = useActiveDescendant({ items: results.map((r) => r.id), getId: (id) => `cmd-${id}`, enabled: open });\n * <input aria-activedescendant={ad.activeDescendantId} onKeyDown={(e) => { if (e.key === 'ArrowDown') ad.next(); }} />\n */\nexport function useActiveDescendant(\n options: UseActiveDescendantOptions,\n): UseActiveDescendantResult {\n const {\n items,\n getId,\n enabled = true,\n fallback = null,\n loop = false,\n activateFirstOnChange = false,\n getElement,\n onScrollToItem,\n onActiveValueChange,\n } = options;\n\n const itemSet = React.useMemo(() => new Set(items), [items]);\n const [movedRaw, setMovedRaw] = React.useState<string | null>(null);\n let moved = enabled ? movedRaw : null;\n\n // activateFirstOnChange: tracked (and updated) only while the option is on, so a render without\n // it adds no render-phase update (C-HOOKS: adjust-during-render).\n const [track, setTrack] = React.useState(() => ({ enabled, items }));\n if (activateFirstOnChange) {\n const itemsChanged = !sameValues(track.items, items);\n if (itemsChanged || track.enabled !== enabled) {\n setTrack({ enabled, items });\n if (enabled && itemsChanged) moved = items[0] ?? null;\n }\n }\n if (moved !== null && !itemSet.has(moved)) moved = null;\n if (moved !== movedRaw) setMovedRaw(moved);\n\n const activeValue = enabled\n ? (moved ?? (fallback !== null && itemSet.has(fallback) ? fallback : null))\n : null;\n const activeDescendantId = activeValue === null ? undefined : getId(activeValue);\n\n // The value the pointer (or a `scroll: false` move) activated, until the scroll effect saw it.\n const noScrollRef = React.useRef<string | null>(null);\n\n const setActiveValue = useEventCallback(\n (value: string | null, moveOptions?: { scroll?: boolean }) => {\n noScrollRef.current = moveOptions?.scroll === false ? value : null;\n setMovedRaw(value);\n },\n );\n const highlight = useEventCallback((value: string) => {\n if (!enabled || !itemSet.has(value) || value === activeValue) return;\n noScrollRef.current = value;\n setMovedRaw(value);\n });\n const first = useEventCallback(() => setActiveValue(items[0] ?? null));\n const last = useEventCallback(() => setActiveValue(items[items.length - 1] ?? null));\n const next = useEventCallback(() => setActiveValue(step(items, activeValue, 1, loop)));\n const prev = useEventCallback(() => setActiveValue(step(items, activeValue, -1, loop)));\n // Never wraps, whatever `loop` is: `loop` is next()/prev()'s wrap at the ends only (fix round 1).\n const move = useEventCallback((delta: number) =>\n setActiveValue(step(items, activeValue, delta, false)),\n );\n\n const resolveElement = useEventCallback((value: string): HTMLElement | null => {\n const custom = getElement?.(value);\n if (custom) return custom;\n return typeof document === 'undefined' ? null : document.getElementById(getId(value));\n });\n // A windowed list (it renders part of its items) gets every item that scrolls by its index, read\n // from the items of the commit.\n const scrollToItem = useEventCallback((value: string) => {\n if (!onScrollToItem) return;\n const index = items.indexOf(value);\n if (index !== -1) onScrollToItem(index);\n });\n React.useLayoutEffect(() => {\n const suppressed = activeValue !== null && activeValue === noScrollRef.current;\n noScrollRef.current = null;\n if (activeValue === null || suppressed) return;\n // The window moves to the item first, in this update, so the frame never paints the new scroll\n // position over the old window; its element, when there is one, is then scrolled into view as\n // well (for the list itself no change by then, its ancestors as before).\n scrollToItem(activeValue);\n resolveElement(activeValue)?.scrollIntoView?.({ block: 'nearest' });\n }, [activeValue, resolveElement, scrollToItem]);\n\n const reportedRef = React.useRef<string | null>(null);\n const report = useEventCallback((value: string | null) => onActiveValueChange?.(value));\n React.useEffect(() => {\n if (reportedRef.current === activeValue) return;\n reportedRef.current = activeValue;\n report(activeValue);\n }, [activeValue, report]);\n\n return {\n activeValue,\n activeDescendantId,\n setActiveValue,\n highlight,\n first,\n last,\n next,\n prev,\n move,\n };\n}\n"],"mappings":"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"}
|
|
@@ -14,40 +14,48 @@ function r(e, t, n, r) {
|
|
|
14
14
|
return (o < 0 || o >= i) && (o = r && Math.abs(n) === 1 ? (o + i) % i : Math.max(0, Math.min(i - 1, o))), e[o];
|
|
15
15
|
}
|
|
16
16
|
function i(i) {
|
|
17
|
-
let { items: a, getId: o, enabled: s = !0, fallback: c = null, loop: l = !1, activateFirstOnChange: u = !1, getElement: d,
|
|
17
|
+
let { items: a, getId: o, enabled: s = !0, fallback: c = null, loop: l = !1, activateFirstOnChange: u = !1, getElement: d, onScrollToItem: f, onActiveValueChange: p } = i, m = t.useMemo(() => new Set(a), [a]), [h, g] = t.useState(null), _ = s ? h : null, [v, y] = t.useState(() => ({
|
|
18
18
|
enabled: s,
|
|
19
19
|
items: a
|
|
20
20
|
}));
|
|
21
21
|
if (u) {
|
|
22
|
-
let e = !n(
|
|
23
|
-
(e ||
|
|
22
|
+
let e = !n(v.items, a);
|
|
23
|
+
(e || v.enabled !== s) && (y({
|
|
24
24
|
enabled: s,
|
|
25
25
|
items: a
|
|
26
|
-
}), s && e && (
|
|
26
|
+
}), s && e && (_ = a[0] ?? null));
|
|
27
27
|
}
|
|
28
|
-
|
|
29
|
-
let
|
|
30
|
-
|
|
31
|
-
}),
|
|
32
|
-
s &&
|
|
33
|
-
}),
|
|
28
|
+
_ !== null && !m.has(_) && (_ = null), _ !== h && g(_);
|
|
29
|
+
let b = s ? _ ?? (c !== null && m.has(c) ? c : null) : null, x = b === null ? void 0 : o(b), S = t.useRef(null), C = e((e, t) => {
|
|
30
|
+
S.current = t?.scroll === !1 ? e : null, g(e);
|
|
31
|
+
}), w = e((e) => {
|
|
32
|
+
s && m.has(e) && e !== b && (S.current = e, g(e));
|
|
33
|
+
}), T = e(() => C(a[0] ?? null)), E = e(() => C(a[a.length - 1] ?? null)), D = e(() => C(r(a, b, 1, l))), O = e(() => C(r(a, b, -1, l))), k = e((e) => C(r(a, b, e, !1))), A = e((e) => d?.(e) || (typeof document > "u" ? null : document.getElementById(o(e)))), j = e((e) => {
|
|
34
|
+
if (!f) return;
|
|
35
|
+
let t = a.indexOf(e);
|
|
36
|
+
t !== -1 && f(t);
|
|
37
|
+
});
|
|
34
38
|
t.useLayoutEffect(() => {
|
|
35
|
-
let e =
|
|
36
|
-
|
|
37
|
-
}, [
|
|
38
|
-
|
|
39
|
+
let e = b !== null && b === S.current;
|
|
40
|
+
S.current = null, !(b === null || e) && (j(b), A(b)?.scrollIntoView?.({ block: "nearest" }));
|
|
41
|
+
}, [
|
|
42
|
+
b,
|
|
43
|
+
A,
|
|
44
|
+
j
|
|
45
|
+
]);
|
|
46
|
+
let M = t.useRef(null), N = e((e) => p?.(e));
|
|
39
47
|
return t.useEffect(() => {
|
|
40
|
-
|
|
41
|
-
}, [
|
|
42
|
-
activeValue:
|
|
43
|
-
activeDescendantId:
|
|
44
|
-
setActiveValue:
|
|
45
|
-
highlight:
|
|
46
|
-
first:
|
|
47
|
-
last:
|
|
48
|
-
next:
|
|
49
|
-
prev:
|
|
50
|
-
move:
|
|
48
|
+
M.current !== b && (M.current = b, N(b));
|
|
49
|
+
}, [b, N]), {
|
|
50
|
+
activeValue: b,
|
|
51
|
+
activeDescendantId: x,
|
|
52
|
+
setActiveValue: C,
|
|
53
|
+
highlight: w,
|
|
54
|
+
first: T,
|
|
55
|
+
last: E,
|
|
56
|
+
next: D,
|
|
57
|
+
prev: O,
|
|
58
|
+
move: k
|
|
51
59
|
};
|
|
52
60
|
}
|
|
53
61
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useActiveDescendant.mjs","names":["React","sameValues","a","b","value","index","step","count","values","current","delta","next","loop","useActiveDescendant","options","itemSet","items","movedRaw","setMovedRaw","moved","enabled","track","setTrack","activateFirstOnChange","itemsChanged","activeValue","fallback","activeDescendantId","getId","noScrollRef","setActiveValue","useEventCallback","moveOptions","highlight","first","last","prev","move","resolveElement","getElement","suppressed","reportedRef","report","onActiveValueChange"],"sources":["../../src/hooks/useActiveDescendant.ts"],"sourcesContent":["import * as React from 'react';\nimport { useEventCallback } from './useEventCallback';\n\n/** Options of {@link useActiveDescendant}. */\nexport interface UseActiveDescendantOptions {\n /** The values that can become active, in navigation order. */\n items: readonly string[];\n /** The DOM id of an item's element: the `aria-activedescendant` target. */\n getId: (value: string) => string;\n /**\n * While `false`, no item is active and the moved-to item is forgotten (a closed popup, an\n * unfocused list). A move made in the same update that enables the hook is kept.\n * @default true\n */\n enabled?: boolean;\n /**\n * The active item while none was moved to, or after the moved-to item left `items` (the\n * selected option, for example). Ignored when it is not in `items`.\n * @default null\n */\n fallback?: string | null;\n /** `next()` and `prev()` wrap at the ends. @default false */\n loop?: boolean;\n /**\n * Whenever `items` changes while the hook is enabled (compared by content), its first item\n * becomes active — also when the change comes with enabling (the keystroke that opens a\n * filtered list). Enabling with unchanged items keeps the fallback. It wins over a move made in\n * the same update.\n * @default false\n */\n activateFirstOnChange?: boolean;\n /** The element of an item, for scrolling it into view. @default document.getElementById(getId(value)) */\n getElement?: (value: string) => HTMLElement | null;\n /** Called after the active item changed (from an effect), `null` included. */\n onActiveValueChange?: (value: string | null) => void;\n}\n\n/** Result of {@link useActiveDescendant}. The methods have stable identities. */\nexport interface UseActiveDescendantResult {\n /** The moved-to item, else the fallback; `null` while disabled. Derived during render. */\n activeValue: string | null;\n /** `getId(activeValue)`, or `undefined`: spread as `aria-activedescendant`. */\n activeDescendantId: string | undefined;\n /**\n * Moves to `value`, or back to the fallback with `null`. The value is kept as it is; the next\n * render drops it when it is not in that render's `items` or the hook is disabled, clearing any\n * earlier move. Scrolled into view unless `scroll` is `false`.\n */\n setActiveValue: (value: string | null, options?: { scroll?: boolean }) => void;\n /**\n * Moves to `value` for pointer movement, not scrolled into view; ignored while disabled, when\n * `value` is not in `items` or is already active.\n */\n highlight: (value: string) => void;\n /** Moves to the first item. */\n first: () => void;\n /** Moves to the last item. */\n last: () => void;\n /** The item after the active one (the first when none is active); wraps with `loop`. */\n next: () => void;\n /** The item before the active one (the last when none is active); wraps with `loop`. */\n prev: () => void;\n /**\n * Moves `delta` items (PageDown: `move(10)`), clamped at the ends, never wrapping; from no active\n * item, a positive delta goes to the first item and a negative one to the last.\n */\n move: (delta: number) => void;\n}\n\nfunction sameValues(a: readonly string[], b: readonly string[]): boolean {\n if (a === b) return true;\n if (a.length !== b.length) return false;\n return a.every((value, index) => value === b[index]);\n}\n\nfunction step(values: readonly string[], current: string | null, delta: number, loop: boolean) {\n const count = values.length;\n if (count === 0) return null;\n const index = current === null ? -1 : values.indexOf(current);\n if (index === -1) return delta > 0 ? values[0] : values[count - 1];\n let next = index + delta;\n if (next < 0 || next >= count) {\n next =\n loop && Math.abs(delta) === 1\n ? (next + count) % count\n : Math.max(0, Math.min(count - 1, next));\n }\n return values[next];\n}\n\n/**\n * The active (virtually focused) item of a composite that keeps DOM focus on one element and points\n * `aria-activedescendant` at the active item: a combobox, a listbox that holds focus, a command\n * palette. The state is React state, so the active id is derived during render (server HTML\n * included) and nothing reads the DOM during render.\n *\n * - The moved-to item is dropped when it leaves `items` or while `enabled` is `false`, and does not\n * come back without a user action; `fallback` is active while nothing was moved to.\n * - Keyboard and programmatic moves are scrolled into view (`{ block: 'nearest' }`); pointer moves\n * (`highlight`) and `setActiveValue(value, { scroll: false })` are not.\n * - `onActiveValueChange` reports every change after the commit, once (StrictMode included).\n *\n * `useListbox` is built on it; use it directly for composites that are not listboxes.\n *\n * @example\n * const ad = useActiveDescendant({ items: results.map((r) => r.id), getId: (id) => `cmd-${id}`, enabled: open });\n * <input aria-activedescendant={ad.activeDescendantId} onKeyDown={(e) => { if (e.key === 'ArrowDown') ad.next(); }} />\n */\nexport function useActiveDescendant(\n options: UseActiveDescendantOptions,\n): UseActiveDescendantResult {\n const {\n items,\n getId,\n enabled = true,\n fallback = null,\n loop = false,\n activateFirstOnChange = false,\n getElement,\n onActiveValueChange,\n } = options;\n\n const itemSet = React.useMemo(() => new Set(items), [items]);\n const [movedRaw, setMovedRaw] = React.useState<string | null>(null);\n let moved = enabled ? movedRaw : null;\n\n // activateFirstOnChange: tracked (and updated) only while the option is on, so a render without\n // it adds no render-phase update (C-HOOKS: adjust-during-render).\n const [track, setTrack] = React.useState(() => ({ enabled, items }));\n if (activateFirstOnChange) {\n const itemsChanged = !sameValues(track.items, items);\n if (itemsChanged || track.enabled !== enabled) {\n setTrack({ enabled, items });\n if (enabled && itemsChanged) moved = items[0] ?? null;\n }\n }\n if (moved !== null && !itemSet.has(moved)) moved = null;\n if (moved !== movedRaw) setMovedRaw(moved);\n\n const activeValue = enabled\n ? (moved ?? (fallback !== null && itemSet.has(fallback) ? fallback : null))\n : null;\n const activeDescendantId = activeValue === null ? undefined : getId(activeValue);\n\n // The value the pointer (or a `scroll: false` move) activated, until the scroll effect saw it.\n const noScrollRef = React.useRef<string | null>(null);\n\n const setActiveValue = useEventCallback(\n (value: string | null, moveOptions?: { scroll?: boolean }) => {\n noScrollRef.current = moveOptions?.scroll === false ? value : null;\n setMovedRaw(value);\n },\n );\n const highlight = useEventCallback((value: string) => {\n if (!enabled || !itemSet.has(value) || value === activeValue) return;\n noScrollRef.current = value;\n setMovedRaw(value);\n });\n const first = useEventCallback(() => setActiveValue(items[0] ?? null));\n const last = useEventCallback(() => setActiveValue(items[items.length - 1] ?? null));\n const next = useEventCallback(() => setActiveValue(step(items, activeValue, 1, loop)));\n const prev = useEventCallback(() => setActiveValue(step(items, activeValue, -1, loop)));\n // Never wraps, whatever `loop` is: `loop` is next()/prev()'s wrap at the ends only (fix round 1).\n const move = useEventCallback((delta: number) =>\n setActiveValue(step(items, activeValue, delta, false)),\n );\n\n const resolveElement = useEventCallback((value: string): HTMLElement | null => {\n const custom = getElement?.(value);\n if (custom) return custom;\n return typeof document === 'undefined' ? null : document.getElementById(getId(value));\n });\n React.useLayoutEffect(() => {\n const suppressed = activeValue !== null && activeValue === noScrollRef.current;\n noScrollRef.current = null;\n if (activeValue === null || suppressed) return;\n resolveElement(activeValue)?.scrollIntoView?.({ block: 'nearest' });\n }, [activeValue, resolveElement]);\n\n const reportedRef = React.useRef<string | null>(null);\n const report = useEventCallback((value: string | null) => onActiveValueChange?.(value));\n React.useEffect(() => {\n if (reportedRef.current === activeValue) return;\n reportedRef.current = activeValue;\n report(activeValue);\n }, [activeValue, report]);\n\n return {\n activeValue,\n activeDescendantId,\n setActiveValue,\n highlight,\n first,\n last,\n next,\n prev,\n move,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useActiveDescendant.mjs","names":["React","sameValues","a","b","value","index","step","count","values","current","delta","next","loop","useActiveDescendant","options","itemSet","items","movedRaw","setMovedRaw","moved","enabled","track","setTrack","activateFirstOnChange","itemsChanged","activeValue","fallback","activeDescendantId","getId","noScrollRef","setActiveValue","useEventCallback","moveOptions","highlight","first","last","prev","move","resolveElement","getElement","scrollToItem","onScrollToItem","suppressed","reportedRef","report","onActiveValueChange"],"sources":["../../src/hooks/useActiveDescendant.ts"],"sourcesContent":["import * as React from 'react';\nimport { useEventCallback } from './useEventCallback';\n\n/** Options of {@link useActiveDescendant}. */\nexport interface UseActiveDescendantOptions {\n /** The values that can become active, in navigation order. */\n items: readonly string[];\n /** The DOM id of an item's element: the `aria-activedescendant` target. */\n getId: (value: string) => string;\n /**\n * While `false`, no item is active and the moved-to item is forgotten (a closed popup, an\n * unfocused list). A move made in the same update that enables the hook is kept.\n * @default true\n */\n enabled?: boolean;\n /**\n * The active item while none was moved to, or after the moved-to item left `items` (the\n * selected option, for example). Ignored when it is not in `items`.\n * @default null\n */\n fallback?: string | null;\n /** `next()` and `prev()` wrap at the ends. @default false */\n loop?: boolean;\n /**\n * Whenever `items` changes while the hook is enabled (compared by content), its first item\n * becomes active — also when the change comes with enabling (the keystroke that opens a\n * filtered list). Enabling with unchanged items keeps the fallback. It wins over a move made in\n * the same update.\n * @default false\n */\n activateFirstOnChange?: boolean;\n /** The element of an item, for scrolling it into view. @default document.getElementById(getId(value)) */\n getElement?: (value: string) => HTMLElement | null;\n /**\n * Called for every move that scrolls the active item into view, for a windowed list that renders\n * part of its items: `index` is the item's index in `items`. Scroll the list so that it shows and\n * renders the item (a virtual window's `scrollToIndex`, which moves the window in the same\n * update); an element, when there is one (`getElement`, then the id lookup), is then scrolled\n * into view as well. `aria-activedescendant` already names the item, and points at its element\n * once it mounts. Called after the same moves that scroll (not after a pointer move or a\n * `scroll: false` move), from a layout effect, once per change of the active item. Without it,\n * an item without an element is not scrolled to.\n */\n onScrollToItem?: (index: number) => void;\n /** Called after the active item changed (from an effect), `null` included. */\n onActiveValueChange?: (value: string | null) => void;\n}\n\n/** Result of {@link useActiveDescendant}. The methods have stable identities. */\nexport interface UseActiveDescendantResult {\n /** The moved-to item, else the fallback; `null` while disabled. Derived during render. */\n activeValue: string | null;\n /** `getId(activeValue)`, or `undefined`: spread as `aria-activedescendant`. */\n activeDescendantId: string | undefined;\n /**\n * Moves to `value`, or back to the fallback with `null`. The value is kept as it is; the next\n * render drops it when it is not in that render's `items` or the hook is disabled, clearing any\n * earlier move. Scrolled into view unless `scroll` is `false`.\n */\n setActiveValue: (value: string | null, options?: { scroll?: boolean }) => void;\n /**\n * Moves to `value` for pointer movement, not scrolled into view; ignored while disabled, when\n * `value` is not in `items` or is already active.\n */\n highlight: (value: string) => void;\n /** Moves to the first item. */\n first: () => void;\n /** Moves to the last item. */\n last: () => void;\n /** The item after the active one (the first when none is active); wraps with `loop`. */\n next: () => void;\n /** The item before the active one (the last when none is active); wraps with `loop`. */\n prev: () => void;\n /**\n * Moves `delta` items (PageDown: `move(10)`), clamped at the ends, never wrapping; from no active\n * item, a positive delta goes to the first item and a negative one to the last.\n */\n move: (delta: number) => void;\n}\n\nfunction sameValues(a: readonly string[], b: readonly string[]): boolean {\n if (a === b) return true;\n if (a.length !== b.length) return false;\n return a.every((value, index) => value === b[index]);\n}\n\nfunction step(values: readonly string[], current: string | null, delta: number, loop: boolean) {\n const count = values.length;\n if (count === 0) return null;\n const index = current === null ? -1 : values.indexOf(current);\n if (index === -1) return delta > 0 ? values[0] : values[count - 1];\n let next = index + delta;\n if (next < 0 || next >= count) {\n next =\n loop && Math.abs(delta) === 1\n ? (next + count) % count\n : Math.max(0, Math.min(count - 1, next));\n }\n return values[next];\n}\n\n/**\n * The active (virtually focused) item of a composite that keeps DOM focus on one element and points\n * `aria-activedescendant` at the active item: a combobox, a listbox that holds focus, a command\n * palette. The state is React state, so the active id is derived during render (server HTML\n * included) and nothing reads the DOM during render.\n *\n * - The moved-to item is dropped when it leaves `items` or while `enabled` is `false`, and does not\n * come back without a user action; `fallback` is active while nothing was moved to.\n * - Keyboard and programmatic moves are scrolled into view (`{ block: 'nearest' }`); pointer moves\n * (`highlight`) and `setActiveValue(value, { scroll: false })` are not. With `onScrollToItem` (a\n * windowed list) each such move is first passed to it by the item's index, also for an item\n * without an element.\n * - `onActiveValueChange` reports every change after the commit, once (StrictMode included).\n *\n * `useListbox` is built on it; use it directly for composites that are not listboxes.\n *\n * @example\n * const ad = useActiveDescendant({ items: results.map((r) => r.id), getId: (id) => `cmd-${id}`, enabled: open });\n * <input aria-activedescendant={ad.activeDescendantId} onKeyDown={(e) => { if (e.key === 'ArrowDown') ad.next(); }} />\n */\nexport function useActiveDescendant(\n options: UseActiveDescendantOptions,\n): UseActiveDescendantResult {\n const {\n items,\n getId,\n enabled = true,\n fallback = null,\n loop = false,\n activateFirstOnChange = false,\n getElement,\n onScrollToItem,\n onActiveValueChange,\n } = options;\n\n const itemSet = React.useMemo(() => new Set(items), [items]);\n const [movedRaw, setMovedRaw] = React.useState<string | null>(null);\n let moved = enabled ? movedRaw : null;\n\n // activateFirstOnChange: tracked (and updated) only while the option is on, so a render without\n // it adds no render-phase update (C-HOOKS: adjust-during-render).\n const [track, setTrack] = React.useState(() => ({ enabled, items }));\n if (activateFirstOnChange) {\n const itemsChanged = !sameValues(track.items, items);\n if (itemsChanged || track.enabled !== enabled) {\n setTrack({ enabled, items });\n if (enabled && itemsChanged) moved = items[0] ?? null;\n }\n }\n if (moved !== null && !itemSet.has(moved)) moved = null;\n if (moved !== movedRaw) setMovedRaw(moved);\n\n const activeValue = enabled\n ? (moved ?? (fallback !== null && itemSet.has(fallback) ? fallback : null))\n : null;\n const activeDescendantId = activeValue === null ? undefined : getId(activeValue);\n\n // The value the pointer (or a `scroll: false` move) activated, until the scroll effect saw it.\n const noScrollRef = React.useRef<string | null>(null);\n\n const setActiveValue = useEventCallback(\n (value: string | null, moveOptions?: { scroll?: boolean }) => {\n noScrollRef.current = moveOptions?.scroll === false ? value : null;\n setMovedRaw(value);\n },\n );\n const highlight = useEventCallback((value: string) => {\n if (!enabled || !itemSet.has(value) || value === activeValue) return;\n noScrollRef.current = value;\n setMovedRaw(value);\n });\n const first = useEventCallback(() => setActiveValue(items[0] ?? null));\n const last = useEventCallback(() => setActiveValue(items[items.length - 1] ?? null));\n const next = useEventCallback(() => setActiveValue(step(items, activeValue, 1, loop)));\n const prev = useEventCallback(() => setActiveValue(step(items, activeValue, -1, loop)));\n // Never wraps, whatever `loop` is: `loop` is next()/prev()'s wrap at the ends only (fix round 1).\n const move = useEventCallback((delta: number) =>\n setActiveValue(step(items, activeValue, delta, false)),\n );\n\n const resolveElement = useEventCallback((value: string): HTMLElement | null => {\n const custom = getElement?.(value);\n if (custom) return custom;\n return typeof document === 'undefined' ? null : document.getElementById(getId(value));\n });\n // A windowed list (it renders part of its items) gets every item that scrolls by its index, read\n // from the items of the commit.\n const scrollToItem = useEventCallback((value: string) => {\n if (!onScrollToItem) return;\n const index = items.indexOf(value);\n if (index !== -1) onScrollToItem(index);\n });\n React.useLayoutEffect(() => {\n const suppressed = activeValue !== null && activeValue === noScrollRef.current;\n noScrollRef.current = null;\n if (activeValue === null || suppressed) return;\n // The window moves to the item first, in this update, so the frame never paints the new scroll\n // position over the old window; its element, when there is one, is then scrolled into view as\n // well (for the list itself no change by then, its ancestors as before).\n scrollToItem(activeValue);\n resolveElement(activeValue)?.scrollIntoView?.({ block: 'nearest' });\n }, [activeValue, resolveElement, scrollToItem]);\n\n const reportedRef = React.useRef<string | null>(null);\n const report = useEventCallback((value: string | null) => onActiveValueChange?.(value));\n React.useEffect(() => {\n if (reportedRef.current === activeValue) return;\n reportedRef.current = activeValue;\n report(activeValue);\n }, [activeValue, report]);\n\n return {\n activeValue,\n activeDescendantId,\n setActiveValue,\n highlight,\n first,\n last,\n next,\n prev,\n move,\n };\n}\n"],"mappings":";;;;AAgFA,SAASC,EAAW,GAAsB,GAA+B;CAGvE,OAFIC,MAAMC,KACND,EAAE,WAAWC,EAAE,UACZD,EAAE,OAAO,GAAO,MAAUE,MAAUD,EAAEE,EAAM;AACrD;AAEA,SAASC,EAAK,GAA2B,GAAwB,GAAe,GAAe;CAC7F,IAAMC,IAAQC,EAAO;CACrB,IAAID,MAAU,GAAG,OAAO;CACxB,IAAMF,IAAQI,MAAY,OAAO,KAAKD,EAAO,QAAQC,CAAO;CAC5D,IAAIJ,MAAU,IAAI,OAAOK,IAAQ,IAAIF,EAAO,KAAKA,EAAOD,IAAQ;CAChE,IAAII,IAAON,IAAQK;CAOnB,QANIC,IAAO,KAAKA,KAAQJ,OACtB,IACEK,KAAQ,KAAK,IAAIF,CAAK,MAAM,KACvBC,IAAOJ,KAASA,IACjB,KAAK,IAAI,GAAG,KAAK,IAAIA,IAAQ,GAAGI,CAAI,CAAC,IAEtCH,EAAOG;AAChB;AAsBA,SAAgBE,EACd,GAC2B;CAC3B,IAAM,EACJ,UACA,UACA,aAAU,IACV,cAAW,MACX,UAAO,IACP,2BAAwB,IACxB,eACA,mBACA,2BACEC,GAEEC,IAAUf,EAAM,cAAc,IAAI,IAAIgB,CAAK,GAAG,CAACA,CAAK,CAAC,GACrD,CAACC,GAAUC,KAAelB,EAAM,SAAwB,IAAI,GAC9DmB,IAAQC,IAAUH,IAAW,MAI3B,CAACI,GAAOC,KAAYtB,EAAM,gBAAgB;EAAE;EAAS;CAAM,EAAE;CACnE,IAAIuB,GAAuB;EACzB,IAAMC,IAAe,CAACvB,EAAWoB,EAAM,OAAOL,CAAK;EACnD,CAAIQ,KAAgBH,EAAM,YAAYD,OACpCE,EAAS;GAAE;GAAS;EAAM,CAAC,GACvBF,KAAWI,MAAc,IAAQR,EAAM,MAAM;CAErD;CAEA,AADIG,MAAU,QAAQ,CAACJ,EAAQ,IAAII,CAAK,MAAG,IAAQ,OAC/CA,MAAUF,KAAUC,EAAYC,CAAK;CAEzC,IAAMM,IAAcL,IACfD,MAAUO,MAAa,QAAQX,EAAQ,IAAIW,CAAQ,IAAIA,IAAW,QACnE,MACEC,IAAqBF,MAAgB,OAAO,KAAA,IAAYG,EAAMH,CAAW,GAGzEI,IAAc7B,EAAM,OAAsB,IAAI,GAE9C8B,IAAiBC,GACpB,GAAsB,MAAuC;EAE5D,AADA,EAAY,UAAUC,GAAa,WAAW,KAAQ5B,IAAQ,MAC9Dc,EAAYd,CAAK;CACnB,CACF,GACM6B,IAAYF,GAAkB,MAAkB;EACpD,AAAKX,KAAYL,EAAQ,IAAIX,CAAK,KAAKA,MAAUqB,MACjD,EAAY,UAAUrB,GACtBc,EAAYd,CAAK;CACnB,CAAC,GACK8B,IAAQH,QAAuBD,EAAed,EAAM,MAAM,IAAI,CAAC,GAC/DmB,IAAOJ,QAAuBD,EAAed,EAAMA,EAAM,SAAS,MAAM,IAAI,CAAC,GAC7EL,IAAOoB,QAAuBD,EAAexB,EAAKU,GAAOS,GAAa,GAAGb,CAAI,CAAC,CAAC,GAC/EwB,IAAOL,QAAuBD,EAAexB,EAAKU,GAAOS,GAAa,IAAIb,CAAI,CAAC,CAAC,GAEhFyB,IAAON,GAAkB,MAC7BD,EAAexB,EAAKU,GAAOS,GAAaf,GAAO,EAAK,CAAC,CACvD,GAEM4B,IAAiBP,GAAkB,MACxBQ,IAAanC,CAAK,MAE1B,OAAO,WAAa,MAAc,OAAO,SAAS,eAAewB,EAAMxB,CAAK,CAAC,EACrF,GAGKoC,IAAeT,GAAkB,MAAkB;EACvD,IAAI,CAACU,GAAgB;EACrB,IAAMpC,IAAQW,EAAM,QAAQZ,CAAK;EACjC,AAAIC,MAAU,MAAIoC,EAAepC,CAAK;CACxC,CAAC;CACD,EAAM,sBAAsB;EAC1B,IAAMqC,IAAajB,MAAgB,QAAQA,MAAgBI,EAAY;EAEvE,AADA,EAAY,UAAU,MAClB,QAAgB,QAAQa,OAI5BF,EAAaf,CAAW,GACxBa,EAAeb,CAAW,CAAC,EAAE,iBAAiB,EAAE,OAAO,UAAU,CAAC;CACpE,GAAG;EAACA;EAAaa;EAAgBE;CAAY,CAAC;CAE9C,IAAMG,IAAc3C,EAAM,OAAsB,IAAI,GAC9C4C,IAASb,GAAkB,MAAyBc,IAAsBzC,CAAK,CAAC;CAOtF,OANAJ,EAAM,gBAAgB;EACpB,AAAI2C,EAAY,YAAYlB,MAC5B,EAAY,UAAUA,GACtBmB,EAAOnB,CAAW;CACpB,GAAG,CAACA,GAAamB,CAAM,CAAC,GAEjB;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use client";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../lib/children.cjs"),n=require("../lib/dev.cjs"),r=require("./useEventCallback.cjs"),i=require("./useId.cjs"),a=require("./useTypeahead.cjs"),o=require("./useMergedRefs.cjs"),s=require("./useActiveDescendant.cjs");let c=require("react");c=e.__toESM(c,1);var l=[],u=new Set;function d(){return l}function f(e,t){if(e===t)return!0;if(e.size!==t.size)return!1;for(let n of e)if(!t.has(n))return!1;return!0}function p(e,t){return e.value===t.value&&e.label===t.label&&e.textValue===t.textValue&&!!e.disabled==!!t.disabled&&!!e.hidden==!!t.hidden}function m(e,t){return e===t||e.length===t.length&&e.every((e,n)=>p(e,t[n]))}function h(e,t){let n=e.element.current,r=t.element.current;if(n&&r&&n!==r&&n.isConnected&&r.isConnected){let e=n.compareDocumentPosition(r);if(e&Node.DOCUMENT_POSITION_FOLLOWING)return-1;if(e&Node.DOCUMENT_POSITION_PRECEDING)return 1}return e.seq-t.seq}var g=class{indexByValue=new Map;registrations=new Set;stateListeners=new Set;itemListeners=new Set;nextIndex=0;nextSeq=0;dirty=!1;flushScheduled=!1;itemsStale=!1;itemsSnapshot=l;sorted=[];observer=null;observed=null;watching=!1;connected=!1;registrationMode=!0;active=null;selected;hidden=u;constructor(e){this.selected=new Set(e)}subscribe=e=>(this.stateListeners.add(e),()=>{this.stateListeners.delete(e)});subscribeItems=e=>(this.itemListeners.add(e),()=>{this.itemListeners.delete(e)});isActive=e=>this.active===e;isSelected=e=>this.selected.has(e);isHidden=e=>this.hidden.has(e);getIndex=e=>{let t=this.indexByValue.get(e);return t===void 0&&(t=this.nextIndex++,this.indexByValue.set(e,t)),t};register=(e,t)=>{let n={item:e,element:t,seq:this.nextSeq++};return this.registrations.add(n),this.markDirty(),()=>{this.registrations.delete(n),this.markDirty()}};getItems=()=>{if(this.itemsStale){this.itemsStale=!1;let e=Array.from(this.registrations).sort(h);this.sorted=e;let t=new Set,n=[];for(let{item:r}of e)t.has(r.value)||(t.add(r.value),n.push(r));m(n,this.itemsSnapshot)||(this.itemsSnapshot=n)}return this.itemsSnapshot};getElement=e=>{for(let{item:t,element:n}of this.registrations){let r=n.current;if(t.value===e&&r&&r.isConnected)return r}return null};setRegistrationMode(e){e!==this.registrationMode&&(this.registrationMode=e,this.syncObserver())}connect(){this.connected=!0,this.syncObserver()}disconnect(){this.connected=!1,this.syncObserver()}flush(){this.dirty&&(this.dirty=!1,this.itemsStale=!0,this.registrationMode&&this.warnDuplicates(),this.syncObserver(),this.notifyItems())}checkOrder(){if(!this.registrationMode||this.dirty||this.itemsStale)return;let e=this.sorted;for(let t=1;t<e.length;t++){let n=e[t-1].element.current,r=e[t].element.current;if(n&&r&&n!==r&&n.isConnected&&r.isConnected&&n.compareDocumentPosition(r)&Node.DOCUMENT_POSITION_PRECEDING){this.itemsStale=!0,this.notifyItems();return}}}checkOrderAfterCommit(){let e=this.observer!==null&&this.observer.takeRecords().length>0,t=this.watching;this.watching=this.observed!==null,(!t||e)&&this.checkOrder()}setRenderState(e,t,n){let r=!1;if(e!==this.active&&(this.active=e,r=!0),f(t,this.selected)||(this.selected=t,r=!0),f(n,this.hidden)||(this.hidden=n,r=!0),r)for(let e of Array.from(this.stateListeners))e()}notifyItems(){for(let e of Array.from(this.itemListeners))e()}onMutation=()=>{this.checkOrder()};syncObserver(){let e=this.connected&&this.registrationMode&&typeof MutationObserver<`u`?this.commonAncestor():null;e!==this.observed&&(this.observer?.disconnect(),this.observed=e,this.watching=!1,e&&(n.isDev&&v(e),this.observer??=new MutationObserver(this.onMutation),this.observer.observe(e,{childList:!0,subtree:!0})))}commonAncestor(){let e=null,t=0;for(let{element:n}of this.registrations){let r=n.current;if(r&&r.isConnected){if(t++,e===null){e=r.parentNode;continue}for(;e&&!e.contains(r);)e=e.parentNode;if(!e)return null}}return t>1?e:null}markDirty(){this.dirty=!0,!this.flushScheduled&&(this.flushScheduled=!0,queueMicrotask(()=>{this.flushScheduled=!1,this.flush()}))}warnDuplicates(){let e=new Set;for(let{item:t}of this.registrations)e.has(t.value)&&_(t.value),e.add(t.value)}};function _(e){n.warnOnce(`useListbox:duplicate:${e}`,`Listbox: several options share the value "${e}". Option values must be unique within a listbox; only the first one can be highlighted and selected.`)}function v(e){let t=e.ownerDocument;!t||e!==t.body&&e!==t.documentElement||n.warnOnce(`useListbox:single-container`,`Listbox: the options of one listbox are rendered in more than one container (for example an inline list kept mounted next to a portaled one). All options must live in a single container at a time: render the list inline only while closed and in the portal only while open.`)}var y=c.createContext(null);y.displayName=`ListboxContext`;var b=null;function x(){return b??={listboxId:`wave-listbox-inert`,store:new g([]),multiselect:!1,mode:`select-only`,select:()=>{},highlight:()=>{},press:()=>{}},b}function S(e){return(0,c.useContext)(y)||(n.reportMissingContext(e,`a listbox (Listbox, Combobox, Dropdown or a ListboxProvider)`),x())}var C=Symbol.for(`@mortenbrudvik/waveui/listbox-element-kind`);function w(e,t){return Object.defineProperty(e,C,{value:t,configurable:!0}),e}function T(e){if(typeof e!=`function`&&typeof e!=`object`||e===null)return;let t=e[C];return t===`option`||t===`group`?t:void 0}function E(e){if(typeof e==`string`)return e;if(typeof e==`number`||typeof e==`bigint`)return String(e);if(e==null||typeof e==`boolean`)return``;if(c.isValidElement(e))return typeof e.type==`string`||e.type===c.Fragment?E(e.props.children):``;if(typeof e==`object`&&Symbol.iterator in e){let t=``;for(let n of e)t+=E(n);return t}return``}function D(e,t){return typeof e.label==`string`?e.label:typeof e.textValue==`string`?e.textValue:e.children===null||e.children===void 0?t:E(e.children).trim()||void 0}function O(e,n){c.Children.forEach(e,e=>{if(!c.isValidElement(e))return;let r=t.getElementType(e);if(r===c.Fragment){O(e.props.children,n);return}let i=T(r);if(i===`group`){O(e.props.children,n);return}if(i!==`option`)return;let{value:a}=e.props;if(typeof a!=`string`||n.has(a))return;let o=D(e.props,a);o!==void 0&&n.set(a,o)})}function k(e){let t=new Map;return O(e,t),t}function A(e){return(e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement)&&e.value!==``}var j=new Set([`x`,`v`,`z`,`y`]);function M(e){let t=e.currentTarget;if((t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement)&&(t.readOnly||t.disabled))return!1;let{key:n}=e;return n===`Backspace`||n===`Delete`||n===`Process`||n===`Unidentified`?!0:n.length===1?!(e.metaKey||e.ctrlKey&&!a.isAltGraphCharacter(e))||j.has(n.toLowerCase()):!1}function N(e){e.preventDefault()}function P(e){let{open:t,onOpenChange:o,mode:l,multiselect:f=!1,selectedValues:p,onSelect:m,items:h,filter:v,loop:y=!1,typeahead:b=l!==`editable`,disabledOptionsFocusable:x=!1,autoHighlight:S=`selected`,highlightOnFilter:C=!1,onActiveValueChange:w,idPrefix:T,onClearDraft:E}=e,D=l===`standalone`,O=D?void 0:o,[k,j]=(0,c.useState)(null),P=i.useId(T??`listbox`),[F]=(0,c.useState)(()=>new g(p)),I=h!==void 0,L=(0,c.useSyncExternalStore)(F.subscribeItems,I?d:F.getItems,d),R=h??L,z=(0,c.useMemo)(()=>R.filter(e=>!e.hidden&&(!v||v(e))),[R,v]),B=(0,c.useMemo)(()=>x?z.map(e=>e.value):z.filter(e=>!e.disabled).map(e=>e.value),[z,x]),V=(0,c.useMemo)(()=>{let e=new Map;for(let t of R)e.has(t.value)||e.set(t.value,t);return e},[R]),H=(0,c.useMemo)(()=>{if(z.length===R.length)return u;let e=new Set(z.map(e=>e.value)),t=new Set;for(let n of R)e.has(n.value)||t.add(n.value);return t},[R,z]),U=new Set(p),W=B.find(e=>U.has(e))??null,G=null;S===`selected`?G=W??B[0]??null:S===`first`&&(G=B[0]??null);let K=(0,c.useCallback)(e=>`${P}-opt-${F.getIndex(e)}`,[P,F]),ee=(0,c.useCallback)(e=>V.get(e),[V]),q=s.useActiveDescendant({items:B,getId:K,enabled:t,fallback:G,loop:y,activateFirstOnChange:C,getElement:F.getElement,onActiveValueChange:w}),{activeValue:J,activeDescendantId:Y}=q,X=r.useEventCallback((e,t,n,r)=>{let i=V.get(e)??r;i&&!i.disabled&&(m(e,{item:i,event:n}),D||f&&t===`select`?q.setActiveValue(e):(q.setActiveValue(null),O?.(!1,{reason:t,event:n})))}),Z=r.useEventCallback((e,t,n)=>{X(e,`select`,n,t)}),Q=r.useEventCallback(e=>{k?k.focus({preventScroll:!0}):D&&n.warnOnce(`useListbox:list-ref`,"useListbox: the list element is unknown, so a pointer press cannot focus the list. Pass the `ref` of `getListboxProps()` to the list element, merged with your own ref."),B.includes(e)?q.setActiveValue(e,{scroll:!1}):!t&&G!==null&&q.setActiveValue(G,{scroll:!1})}),$=(0,c.useRef)(null),{onTypeahead:te}=a.useTypeahead({getItems:()=>z.map(e=>({value:e.value,text:e.textValue??e.label,disabled:!x&&e.disabled})),onMatch:e=>{q.setActiveValue(e);let n=$.current;!t&&n&&O?.(!0,{reason:`keyboard`,event:n})}}),ne=r.useEventCallback(e=>{if(e.defaultPrevented||e.nativeEvent.isComposing)return;$.current=e.nativeEvent,l===`editable`&&t&&M(e)&&q.setActiveValue(C?B[0]??null:null);let n=a.isAltGraphCharacter(e);if(e.ctrlKey&&!n||e.metaKey)return;let{key:r,altKey:i}=e;if(D&&(r===`Tab`||r===`Escape`||i&&r.startsWith(`Arrow`)))return;let o=l===`select-only`,s=l===`editable`,c=B[0]??null,u=B[B.length-1]??null,d=n=>{q.setActiveValue(n),t||O?.(!0,{reason:`keyboard`,event:e.nativeEvent})},p=()=>{J===null?f||O?.(!1,{reason:`keyboard`,event:e.nativeEvent}):X(J,`select`,e.nativeEvent)};switch(r){case`ArrowDown`:e.preventDefault(),i?t||O?.(!0,{reason:`keyboard`,event:e.nativeEvent}):t?q.next():d(W??c);return;case`ArrowUp`:if(e.preventDefault(),i){if(!t)return;o&&!f&&J!==null&&!V.get(J)?.disabled?X(J,`select`,e.nativeEvent):O?.(!1,{reason:`keyboard`,event:e.nativeEvent})}else t?q.prev():d(W??u);return;case`Home`:case`End`:if(s)return;e.preventDefault(),d(r===`Home`?c:u);return;case`PageUp`:case`PageDown`:if(s||!t)return;e.preventDefault(),q.move(r===`PageDown`?10:-10);return;case`Enter`:if(s){t&&J!==null&&(e.preventDefault(),X(J,`select`,e.nativeEvent));return}e.preventDefault(),t?p():O?.(!0,{reason:`keyboard`,event:e.nativeEvent});return;case`Escape`:t?(e.preventDefault(),O?.(!1,{reason:`escape`,event:e.nativeEvent})):s&&E&&A(e.currentTarget)&&(e.preventDefault(),E());return;case`Tab`:if(!t)return;o&&!f&&J!==null&&!V.get(J)?.disabled?X(J,`tab`,e.nativeEvent):O?.(!1,{reason:`tab`,event:e.nativeEvent});return}if(!(r===` `&&s)){if(!b||i&&!n||r.length!==1){r===` `&&!s&&(e.preventDefault(),t?p():O?.(!0,{reason:`keyboard`,event:e.nativeEvent}));return}if(te(e,t?J:W)){e.preventDefault();return}if(r===` `){e.preventDefault(),t?p():O?.(!0,{reason:`keyboard`,event:e.nativeEvent});return}o&&!t&&(e.preventDefault(),O?.(!0,{reason:`keyboard`,event:e.nativeEvent}))}}),re=r.useEventCallback(e=>{l===`select-only`&&e.key===` `&&e.preventDefault()});(0,c.useLayoutEffect)(()=>{F.setRegistrationMode(!I),F.flush(),F.checkOrderAfterCommit(),F.setRenderState(J,U,H)}),(0,c.useLayoutEffect)(()=>(F.connect(),()=>F.disconnect()),[F]),(0,c.useEffect)(()=>{if(!h)return;let e=new Set;for(let t of h)e.has(t.value)&&_(t.value),e.add(t.value)},[h]);let ie=(0,c.useMemo)(()=>({listboxId:P,store:F,multiselect:f,mode:l,select:Z,highlight:q.highlight,press:Q}),[P,F,f,l,Z,q.highlight,Q]);return{listboxId:P,activeValue:J,activeDescendantId:Y,items:z,getItem:ee,getOptionId:K,setActiveValue:q.setActiveValue,onKeyDown:ne,onKeyUp:re,getComboboxProps:()=>{let e={role:`combobox`,"aria-expanded":t,"aria-controls":P,"aria-haspopup":`listbox`};return Y!==void 0&&(e[`aria-activedescendant`]=Y),l===`editable`&&(e[`aria-autocomplete`]=`list`),e},getListboxProps:()=>{let e=D?{id:P,role:`listbox`,tabIndex:0,ref:j}:{id:P,role:`listbox`,tabIndex:-1,onMouseDown:N};return D&&Y!==void 0&&(e[`aria-activedescendant`]=Y),f&&(e[`aria-multiselectable`]=!0),e},context:ie}}var F=1,I=2,L=4;function R(e,t){return(e.isActive(t)?F:0)|(e.isSelected(t)?I:0)|(e.isHidden(t)?L:0)}function z(e,t){let{store:n,listboxId:r,multiselect:i,mode:a,select:s,highlight:l,press:u}=S(`Option`),{value:d,label:f,textValue:m,disabled:h=!1,hidden:g=!1}=e,_=`${r}-opt-${n.getIndex(d)}`,v=(0,c.useSyncExternalStore)(n.subscribe,()=>R(n,d),()=>R(n,d)),y=(v&F)!==0,b=(v&I)!==0,x=g||(v&L)!==0,C=(0,c.useRef)(null),w=(0,c.useCallback)(e=>{C.current=e},[]),T=o.useMergedRefs(t,w),E=(0,c.useRef)(null);(0,c.useLayoutEffect)(()=>{let e=f===void 0&&m===void 0?C.current?.textContent?.trim():void 0,t={value:d,label:f??m??(e||d)};m!==void 0&&(t.textValue=m),h&&(t.disabled=!0),g&&(t.hidden=!0);let r=E.current;r&&r.store===n&&p(r.item,t)||(r?.unregister(),E.current={store:n,item:t,unregister:n.register(t,C)})}),(0,c.useLayoutEffect)(()=>()=>{E.current?.unregister(),E.current=null},[]);let D=(0,c.useCallback)(e=>{if(h)return;let t={value:d,label:f??m??d};m!==void 0&&(t.textValue=m),s(d,t,e.nativeEvent)},[s,d,f,m,h]),O=(0,c.useCallback)(()=>{!h&&!n.isActive(d)&&l(d)},[l,n,d,h]),k=(0,c.useCallback)(e=>{e.button===0&&(e.preventDefault(),u(d))},[u,d]),A={id:_,role:`option`,"aria-selected":b,onClick:D,onPointerMove:O,ref:T};return a===`standalone`&&(A.onMouseDown=k),h&&(A[`aria-disabled`]=!0,A[`data-disabled`]=``),x&&(A.hidden=!0),y&&(A[`data-active`]=``),b&&(A[`data-selected`]=``),{id:_,selected:b,active:y,disabled:h,hidden:x,multiselect:i,optionProps:A}}exports.ListboxContext=y,exports.collectOptionLabels=k,exports.markListboxElement=w,exports.useListbox=P,exports.useListboxOption=z;
|
|
1
|
+
"use client";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../lib/children.cjs"),n=require("../lib/dev.cjs"),r=require("./useEventCallback.cjs"),i=require("./useId.cjs"),a=require("./useTypeahead.cjs"),o=require("./useMergedRefs.cjs"),s=require("./useActiveDescendant.cjs");let c=require("react");c=e.__toESM(c,1);var l=[],u=new Set;function d(){return l}function f(e,t){if(e===t)return!0;if(e.size!==t.size)return!1;for(let n of e)if(!t.has(n))return!1;return!0}function p(e,t){return e.value===t.value&&e.label===t.label&&e.textValue===t.textValue&&!!e.disabled==!!t.disabled&&!!e.hidden==!!t.hidden}function m(e,t){return e===t||e.length===t.length&&e.every((e,n)=>p(e,t[n]))}function h(e,t){let n=e.element.current,r=t.element.current;if(n&&r&&n!==r&&n.isConnected&&r.isConnected){let e=n.compareDocumentPosition(r);if(e&Node.DOCUMENT_POSITION_FOLLOWING)return-1;if(e&Node.DOCUMENT_POSITION_PRECEDING)return 1}return e.seq-t.seq}var g=class{indexByValue=new Map;registrations=new Set;stateListeners=new Set;itemListeners=new Set;nextIndex=0;nextSeq=0;dirty=!1;flushScheduled=!1;itemsStale=!1;itemsSnapshot=l;sorted=[];observer=null;observed=null;watching=!1;connected=!1;registrationMode=!0;active=null;selected;hidden=u;constructor(e){this.selected=new Set(e)}subscribe=e=>(this.stateListeners.add(e),()=>{this.stateListeners.delete(e)});subscribeItems=e=>(this.itemListeners.add(e),()=>{this.itemListeners.delete(e)});isActive=e=>this.active===e;isSelected=e=>this.selected.has(e);isHidden=e=>this.hidden.has(e);getIndex=e=>{let t=this.indexByValue.get(e);return t===void 0&&(t=this.nextIndex++,this.indexByValue.set(e,t)),t};register=(e,t)=>{let n={item:e,element:t,seq:this.nextSeq++};return this.registrations.add(n),this.markDirty(),()=>{this.registrations.delete(n),this.markDirty()}};getItems=()=>{if(this.itemsStale){this.itemsStale=!1;let e=Array.from(this.registrations).sort(h);this.sorted=e;let t=new Set,n=[];for(let{item:r}of e)t.has(r.value)||(t.add(r.value),n.push(r));m(n,this.itemsSnapshot)||(this.itemsSnapshot=n)}return this.itemsSnapshot};getElement=e=>{for(let{item:t,element:n}of this.registrations){let r=n.current;if(t.value===e&&r&&r.isConnected)return r}return null};setRegistrationMode(e){e!==this.registrationMode&&(this.registrationMode=e,this.syncObserver())}connect(){this.connected=!0,this.syncObserver()}disconnect(){this.connected=!1,this.syncObserver()}flush(){this.dirty&&(this.dirty=!1,this.itemsStale=!0,this.registrationMode&&this.warnDuplicates(),this.syncObserver(),this.notifyItems())}checkOrder(){if(!this.registrationMode||this.dirty||this.itemsStale)return;let e=this.sorted;for(let t=1;t<e.length;t++){let n=e[t-1].element.current,r=e[t].element.current;if(n&&r&&n!==r&&n.isConnected&&r.isConnected&&n.compareDocumentPosition(r)&Node.DOCUMENT_POSITION_PRECEDING){this.itemsStale=!0,this.notifyItems();return}}}checkOrderAfterCommit(){let e=this.observer!==null&&this.observer.takeRecords().length>0,t=this.watching;this.watching=this.observed!==null,(!t||e)&&this.checkOrder()}setRenderState(e,t,n){let r=!1;if(e!==this.active&&(this.active=e,r=!0),f(t,this.selected)||(this.selected=t,r=!0),f(n,this.hidden)||(this.hidden=n,r=!0),r)for(let e of Array.from(this.stateListeners))e()}notifyItems(){for(let e of Array.from(this.itemListeners))e()}onMutation=()=>{this.checkOrder()};syncObserver(){let e=this.connected&&this.registrationMode&&typeof MutationObserver<`u`?this.commonAncestor():null;e!==this.observed&&(this.observer?.disconnect(),this.observed=e,this.watching=!1,e&&(n.isDev&&v(e),this.observer??=new MutationObserver(this.onMutation),this.observer.observe(e,{childList:!0,subtree:!0})))}commonAncestor(){let e=null,t=0;for(let{element:n}of this.registrations){let r=n.current;if(r&&r.isConnected){if(t++,e===null){e=r.parentNode;continue}for(;e&&!e.contains(r);)e=e.parentNode;if(!e)return null}}return t>1?e:null}markDirty(){this.dirty=!0,!this.flushScheduled&&(this.flushScheduled=!0,queueMicrotask(()=>{this.flushScheduled=!1,this.flush()}))}warnDuplicates(){let e=new Set;for(let{item:t}of this.registrations)e.has(t.value)&&_(t.value),e.add(t.value)}};function _(e){n.warnOnce(`useListbox:duplicate:${e}`,`Listbox: several options share the value "${e}". Option values must be unique within a listbox; only the first one can be highlighted and selected.`)}function v(e){let t=e.ownerDocument;!t||e!==t.body&&e!==t.documentElement||n.warnOnce(`useListbox:single-container`,`Listbox: the options of one listbox are rendered in more than one container (for example an inline list kept mounted next to a portaled one). All options must live in a single container at a time: render the list inline only while closed and in the portal only while open.`)}var y=c.createContext(null);y.displayName=`ListboxContext`;var b=c.createContext(null);b.displayName=`ListboxOptionPositionContext`;var x=null;function S(){return x??={listboxId:`wave-listbox-inert`,store:new g([]),multiselect:!1,mode:`select-only`,select:()=>{},highlight:()=>{},press:()=>{}},x}function C(e){return(0,c.useContext)(y)||(n.reportMissingContext(e,`a listbox (Listbox, Combobox, Dropdown or a ListboxProvider)`),S())}var w=Symbol.for(`@mortenbrudvik/waveui/listbox-element-kind`);function T(e,t){return Object.defineProperty(e,w,{value:t,configurable:!0}),e}function E(e){if(typeof e!=`function`&&typeof e!=`object`||e===null)return;let t=e[w];return t===`option`||t===`group`?t:void 0}function D(e){if(typeof e==`string`)return e;if(typeof e==`number`||typeof e==`bigint`)return String(e);if(e==null||typeof e==`boolean`)return``;if(c.isValidElement(e))return typeof e.type==`string`||e.type===c.Fragment?D(e.props.children):``;if(typeof e==`object`&&Symbol.iterator in e){let t=``;for(let n of e)t+=D(n);return t}return``}function O(e,t){return typeof e.label==`string`?e.label:typeof e.textValue==`string`?e.textValue:e.children===null||e.children===void 0?t:D(e.children).trim()||void 0}function k(e,n){c.Children.forEach(e,e=>{if(!c.isValidElement(e))return;let r=t.getElementType(e);if(r===c.Fragment){k(e.props.children,n);return}let i=E(r);if(i===`group`){k(e.props.children,n);return}if(i!==`option`)return;let{value:a}=e.props;if(typeof a!=`string`||n.has(a))return;let o=O(e.props,a);o!==void 0&&n.set(a,o)})}function A(e){let t=new Map;return k(e,t),t}function j(e){return(e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement)&&e.value!==``}var M=new Set([`x`,`v`,`z`,`y`]);function ee(e){let t=e.currentTarget;if((t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement)&&(t.readOnly||t.disabled))return!1;let{key:n}=e;return n===`Backspace`||n===`Delete`||n===`Process`||n===`Unidentified`?!0:n.length===1?!(e.metaKey||e.ctrlKey&&!a.isAltGraphCharacter(e))||M.has(n.toLowerCase()):!1}function N(e){e.preventDefault()}function P(e){let{open:t,onOpenChange:o,mode:l,multiselect:f=!1,selectedValues:p,onSelect:m,items:h,filter:v,loop:y=!1,typeahead:b=l!==`editable`,disabledOptionsFocusable:x=!1,autoHighlight:S=`selected`,highlightOnFilter:C=!1,onActiveValueChange:w,onScrollToItem:T,idPrefix:E,onClearDraft:D}=e,O=l===`standalone`,k=O?void 0:o,[A,M]=(0,c.useState)(null),P=i.useId(E??`listbox`),[F]=(0,c.useState)(()=>new g(p)),I=h!==void 0,L=(0,c.useSyncExternalStore)(F.subscribeItems,I?d:F.getItems,d),R=h??L,z=(0,c.useMemo)(()=>R.filter(e=>!e.hidden&&(!v||v(e))),[R,v]),B=(0,c.useMemo)(()=>x?z.map(e=>e.value):z.filter(e=>!e.disabled).map(e=>e.value),[z,x]),V=(0,c.useMemo)(()=>{let e=new Map;for(let t of R)e.has(t.value)||e.set(t.value,t);return e},[R]),te=(0,c.useMemo)(()=>{if(z.length===R.length)return u;let e=new Set(z.map(e=>e.value)),t=new Set;for(let n of R)e.has(n.value)||t.add(n.value);return t},[R,z]),H=new Set(p),U=B.find(e=>H.has(e))??null,W=null;S===`selected`?W=U??B[0]??null:S===`first`&&(W=B[0]??null);let G=(0,c.useCallback)(e=>`${P}-opt-${F.getIndex(e)}`,[P,F]),ne=(0,c.useCallback)(e=>V.get(e),[V]),K=r.useEventCallback(e=>{let t=B[e],n=z.findIndex(e=>e.value===t);n!==-1&&T?.(n)}),q=s.useActiveDescendant({items:B,getId:G,enabled:t,fallback:W,loop:y,activateFirstOnChange:C,getElement:F.getElement,onScrollToItem:T?K:void 0,onActiveValueChange:w}),{activeValue:J,activeDescendantId:Y}=q,X=r.useEventCallback((e,t,n,r)=>{let i=V.get(e)??r;i&&!i.disabled&&(m(e,{item:i,event:n}),O||f&&t===`select`?q.setActiveValue(e):(q.setActiveValue(null),k?.(!1,{reason:t,event:n})))}),Z=r.useEventCallback((e,t,n)=>{X(e,`select`,n,t)}),Q=r.useEventCallback(e=>{A?A.focus({preventScroll:!0}):O&&n.warnOnce(`useListbox:list-ref`,"useListbox: the list element is unknown, so a pointer press cannot focus the list. Pass the `ref` of `getListboxProps()` to the list element, merged with your own ref."),B.includes(e)?q.setActiveValue(e,{scroll:!1}):!t&&W!==null&&q.setActiveValue(W,{scroll:!1})}),$=(0,c.useRef)(null),{onTypeahead:re}=a.useTypeahead({getItems:()=>z.map(e=>({value:e.value,text:e.textValue??e.label,disabled:!x&&e.disabled})),onMatch:e=>{q.setActiveValue(e);let n=$.current;!t&&n&&k?.(!0,{reason:`keyboard`,event:n})}}),ie=r.useEventCallback(e=>{if(e.defaultPrevented||e.nativeEvent.isComposing)return;$.current=e.nativeEvent,l===`editable`&&t&&ee(e)&&q.setActiveValue(C?B[0]??null:null);let n=a.isAltGraphCharacter(e);if(e.ctrlKey&&!n||e.metaKey)return;let{key:r,altKey:i}=e;if(O&&(r===`Tab`||r===`Escape`||i&&r.startsWith(`Arrow`)))return;let o=l===`select-only`,s=l===`editable`,c=B[0]??null,u=B[B.length-1]??null,d=n=>{q.setActiveValue(n),t||k?.(!0,{reason:`keyboard`,event:e.nativeEvent})},p=()=>{J===null?f||k?.(!1,{reason:`keyboard`,event:e.nativeEvent}):X(J,`select`,e.nativeEvent)};switch(r){case`ArrowDown`:e.preventDefault(),i?t||k?.(!0,{reason:`keyboard`,event:e.nativeEvent}):t?q.next():d(U??c);return;case`ArrowUp`:if(e.preventDefault(),i){if(!t)return;o&&!f&&J!==null&&!V.get(J)?.disabled?X(J,`select`,e.nativeEvent):k?.(!1,{reason:`keyboard`,event:e.nativeEvent})}else t?q.prev():d(U??u);return;case`Home`:case`End`:if(s)return;e.preventDefault(),d(r===`Home`?c:u);return;case`PageUp`:case`PageDown`:if(s||!t)return;e.preventDefault(),q.move(r===`PageDown`?10:-10);return;case`Enter`:if(s){t&&J!==null&&(e.preventDefault(),X(J,`select`,e.nativeEvent));return}e.preventDefault(),t?p():k?.(!0,{reason:`keyboard`,event:e.nativeEvent});return;case`Escape`:t?(e.preventDefault(),k?.(!1,{reason:`escape`,event:e.nativeEvent})):s&&D&&j(e.currentTarget)&&(e.preventDefault(),D());return;case`Tab`:if(!t)return;o&&!f&&J!==null&&!V.get(J)?.disabled?X(J,`tab`,e.nativeEvent):k?.(!1,{reason:`tab`,event:e.nativeEvent});return}if(!(r===` `&&s)){if(!b||i&&!n||r.length!==1){r===` `&&!s&&(e.preventDefault(),t?p():k?.(!0,{reason:`keyboard`,event:e.nativeEvent}));return}if(re(e,t?J:U)){e.preventDefault();return}if(r===` `){e.preventDefault(),t?p():k?.(!0,{reason:`keyboard`,event:e.nativeEvent});return}o&&!t&&(e.preventDefault(),k?.(!0,{reason:`keyboard`,event:e.nativeEvent}))}}),ae=r.useEventCallback(e=>{l===`select-only`&&e.key===` `&&e.preventDefault()});(0,c.useLayoutEffect)(()=>{F.setRegistrationMode(!I),F.flush(),F.checkOrderAfterCommit(),F.setRenderState(J,H,te)}),(0,c.useLayoutEffect)(()=>(F.connect(),()=>F.disconnect()),[F]),(0,c.useEffect)(()=>{if(!h)return;let e=new Set;for(let t of h)e.has(t.value)&&_(t.value),e.add(t.value)},[h]);let oe=(0,c.useMemo)(()=>({listboxId:P,store:F,multiselect:f,mode:l,select:Z,highlight:q.highlight,press:Q}),[P,F,f,l,Z,q.highlight,Q]);return{listboxId:P,activeValue:J,activeDescendantId:Y,items:z,getItem:ne,getOptionId:G,setActiveValue:q.setActiveValue,onKeyDown:ie,onKeyUp:ae,getComboboxProps:()=>{let e={role:`combobox`,"aria-expanded":t,"aria-controls":P,"aria-haspopup":`listbox`};return Y!==void 0&&(e[`aria-activedescendant`]=Y),l===`editable`&&(e[`aria-autocomplete`]=`list`),e},getListboxProps:()=>{let e=O?{id:P,role:`listbox`,tabIndex:0,ref:M}:{id:P,role:`listbox`,tabIndex:-1,onMouseDown:N};return O&&Y!==void 0&&(e[`aria-activedescendant`]=Y),f&&(e[`aria-multiselectable`]=!0),e},context:oe}}var F=1,I=2,L=4;function R(e,t){return(e.isActive(t)?F:0)|(e.isSelected(t)?I:0)|(e.isHidden(t)?L:0)}function z(e,t){let{store:n,listboxId:r,multiselect:i,mode:a,select:s,highlight:l,press:u}=C(`Option`),d=(0,c.useContext)(b),{value:f,label:m,textValue:h,disabled:g=!1,hidden:_=!1}=e,v=`${r}-opt-${n.getIndex(f)}`,y=(0,c.useSyncExternalStore)(n.subscribe,()=>R(n,f),()=>R(n,f)),x=(y&F)!==0,S=(y&I)!==0,w=_||(y&L)!==0,T=(0,c.useRef)(null),E=(0,c.useCallback)(e=>{T.current=e},[]),D=o.useMergedRefs(t,E),O=(0,c.useRef)(null);(0,c.useLayoutEffect)(()=>{let e=m===void 0&&h===void 0?T.current?.textContent?.trim():void 0,t={value:f,label:m??h??(e||f)};h!==void 0&&(t.textValue=h),g&&(t.disabled=!0),_&&(t.hidden=!0);let r=O.current;r&&r.store===n&&p(r.item,t)||(r?.unregister(),O.current={store:n,item:t,unregister:n.register(t,T)})}),(0,c.useLayoutEffect)(()=>()=>{O.current?.unregister(),O.current=null},[]);let k=(0,c.useCallback)(e=>{if(g)return;let t={value:f,label:m??h??f};h!==void 0&&(t.textValue=h),s(f,t,e.nativeEvent)},[s,f,m,h,g]),A=(0,c.useCallback)(()=>{!g&&!n.isActive(f)&&l(f)},[l,n,f,g]),j=(0,c.useCallback)(e=>{e.button===0&&(e.preventDefault(),u(f))},[u,f]),M={id:v,role:`option`,"aria-selected":S,onClick:k,onPointerMove:A,ref:D};return a===`standalone`&&(M.onMouseDown=j),d&&(M[`aria-setsize`]=d.setSize,M[`aria-posinset`]=d.index+1,d.itemSize!==void 0&&(M.style={height:d.itemSize,boxSizing:`border-box`,flexShrink:0})),g&&(M[`aria-disabled`]=!0,M[`data-disabled`]=``),w&&(M.hidden=!0),x&&(M[`data-active`]=``),S&&(M[`data-selected`]=``),{id:v,selected:S,active:x,disabled:g,hidden:w,multiselect:i,optionProps:M}}exports.ListboxContext=y,exports.ListboxOptionPositionContext=b,exports.collectOptionLabels=A,exports.markListboxElement=T,exports.useListbox=P,exports.useListboxOption=z;
|
|
2
2
|
//# sourceMappingURL=useListbox.cjs.map
|