@astryxdesign/core 0.1.1 → 0.1.2-canary.03536f1
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/CHANGELOG.md +105 -0
- package/README.md +119 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.js +4 -1
- package/dist/Banner/Banner.d.ts +7 -0
- package/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +9 -2
- package/dist/Button/Button.d.ts +12 -2
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +51 -13
- package/dist/Calendar/Calendar.d.ts +5 -4
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +6 -1
- package/dist/Calendar/index.d.ts +1 -1
- package/dist/Calendar/index.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.d.ts +7 -2
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +5 -4
- package/dist/EmptyState/EmptyState.d.ts.map +1 -1
- package/dist/EmptyState/EmptyState.js +7 -1
- package/dist/HoverCard/HoverCard.d.ts +2 -2
- package/dist/HoverCard/HoverCard.d.ts.map +1 -1
- package/dist/HoverCard/HoverCard.js +18 -6
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +7 -3
- package/dist/Kbd/Kbd.d.ts.map +1 -1
- package/dist/Kbd/Kbd.js +1 -1
- package/dist/Layer/anchorName.d.ts +20 -0
- package/dist/Layer/anchorName.d.ts.map +1 -0
- package/dist/Layer/anchorName.js +36 -0
- package/dist/Layer/useLayer.d.ts +19 -0
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +15 -6
- package/dist/Link/Link.d.ts +10 -1
- package/dist/Link/Link.d.ts.map +1 -1
- package/dist/Link/Link.js +7 -2
- package/dist/Link/LinkProvider.d.ts +1 -0
- package/dist/Link/LinkProvider.d.ts.map +1 -1
- package/dist/Link/LinkProvider.js +1 -0
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +13 -3
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +13 -0
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +31 -27
- package/dist/Slider/Slider.js +2 -2
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +38 -16
- package/dist/Table/Table.d.ts.map +1 -1
- package/dist/Table/Table.js +30 -7
- package/dist/Table/TableCell.d.ts +8 -1
- package/dist/Table/TableCell.d.ts.map +1 -1
- package/dist/Table/TableCell.js +10 -10
- package/dist/Table/TableHeaderCell.d.ts +8 -1
- package/dist/Table/TableHeaderCell.d.ts.map +1 -1
- package/dist/Table/TableHeaderCell.js +16 -16
- package/dist/Table/TableRow.d.ts +6 -1
- package/dist/Table/TableRow.d.ts.map +1 -1
- package/dist/Table/TableRow.js +15 -7
- package/dist/Table/index.d.ts +4 -1
- package/dist/Table/index.d.ts.map +1 -1
- package/dist/Table/index.js +2 -0
- package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
- package/dist/Table/plugins/sortable/useTableSortable.js +59 -3
- package/dist/Table/plugins/stickyColumns/index.d.ts +3 -0
- package/dist/Table/plugins/stickyColumns/index.d.ts.map +1 -0
- package/dist/Table/plugins/stickyColumns/index.js +3 -0
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts +25 -0
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts.map +1 -0
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +369 -0
- package/dist/Table/tableContextMenu.d.ts +33 -0
- package/dist/Table/tableContextMenu.d.ts.map +1 -0
- package/dist/Table/tableContextMenu.js +136 -0
- package/dist/Table/types.d.ts +161 -5
- package/dist/Table/types.d.ts.map +1 -1
- package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
- package/dist/Table/useBaseTablePlugins.js +1 -1
- package/dist/Text/text.stylex.d.ts +2 -2
- package/dist/Text/text.stylex.js +2 -2
- package/dist/Timestamp/Timestamp.d.ts +1 -1
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +28 -3
- package/dist/ToggleButton/ToggleButton.d.ts +7 -9
- package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButton/ToggleButton.js +34 -57
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +1 -1
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +1 -0
- package/dist/astryx.css +30 -1
- package/dist/astryx.umd.js +147 -0
- package/dist/astryx.umd.js.map +7 -0
- package/dist/theme/generateThemeRules.js +1 -1
- package/dist/theme/syntax/SyntaxTheme.d.ts +1 -1
- package/dist/theme/syntax/SyntaxTheme.js +1 -1
- package/dist/theme/syntax/defineSyntaxTheme.d.ts.map +1 -1
- package/dist/theme/syntax/defineSyntaxTheme.js +2 -2
- package/dist/theme/syntax/presets.d.ts +1 -1
- package/dist/theme/syntax/presets.d.ts.map +1 -1
- package/dist/theme/syntax/presets.js +1 -1
- package/dist/theme/syntax/tokens.d.ts +1 -1
- package/dist/theme/syntax/tokens.js +1 -1
- package/dist/theme/types.d.ts +1 -1
- package/dist/utils/dateTypes.d.ts +12 -0
- package/dist/utils/dateTypes.d.ts.map +1 -1
- package/dist/utils/dateTypes.js +41 -1
- package/package.json +6 -2
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +3 -0
- package/src/Banner/Banner.doc.mjs +8 -0
- package/src/Banner/Banner.test.tsx +16 -7
- package/src/Banner/Banner.tsx +9 -2
- package/src/Button/Button.doc.mjs +6 -0
- package/src/Button/Button.test.tsx +105 -11
- package/src/Button/Button.tsx +73 -5
- package/src/Calendar/Calendar.doc.mjs +4 -4
- package/src/Calendar/Calendar.test.tsx +21 -26
- package/src/Calendar/Calendar.tsx +21 -5
- package/src/Calendar/index.ts +1 -0
- package/src/Chat/ChatComposer.doc.mjs +3 -0
- package/src/Chat/ChatComposerDrawer.doc.mjs +12 -0
- package/src/CheckboxInput/CheckboxInput.doc.mjs +8 -0
- package/src/ClickableCard/ClickableCard.doc.mjs +15 -0
- package/src/DateTimeInput/DateTimeInput.doc.mjs +11 -3
- package/src/DateTimeInput/DateTimeInput.test.tsx +23 -3
- package/src/DateTimeInput/DateTimeInput.tsx +13 -10
- package/src/Dialog/Dialog.doc.mjs +2 -2
- package/src/EmptyState/EmptyState.test.tsx +4 -2
- package/src/EmptyState/EmptyState.tsx +6 -2
- package/src/HoverCard/HoverCard.doc.mjs +3 -0
- package/src/HoverCard/HoverCard.test.tsx +189 -2
- package/src/HoverCard/HoverCard.tsx +20 -16
- package/src/HoverCard/useHoverCard.tsx +13 -10
- package/src/Icon/Icon.doc.mjs +8 -0
- package/src/Kbd/Kbd.tsx +2 -9
- package/src/Layer/anchorName.test.ts +146 -0
- package/src/Layer/anchorName.ts +46 -0
- package/src/Layer/useLayer.doc.mjs +7 -2
- package/src/Layer/useLayer.tsx +33 -9
- package/src/Layout/Layout.doc.mjs +1 -1
- package/src/Lightbox/Lightbox.doc.mjs +0 -2
- package/src/Link/Link.test.tsx +20 -2
- package/src/Link/Link.tsx +12 -3
- package/src/Link/LinkProvider.tsx +1 -0
- package/src/List/List.doc.mjs +7 -0
- package/src/Markdown/Markdown.doc.mjs +2 -0
- package/src/Markdown/Markdown.test.tsx +17 -26
- package/src/Markdown/Markdown.tsx +16 -6
- package/src/MobileNav/MobileNav.doc.mjs +8 -8
- package/src/MobileNav/MobileNav.tsx +13 -0
- package/src/MobileNav/MobileNavReopen.test.tsx +118 -0
- package/src/OverflowList/OverflowList.doc.mjs +17 -0
- package/src/Pagination/Pagination.test.tsx +137 -13
- package/src/Pagination/Pagination.tsx +33 -28
- package/src/ProgressBar/ProgressBar.test.tsx +13 -3
- package/src/Resizable/Resizable.doc.mjs +1 -1
- package/src/Section/Section.doc.mjs +11 -0
- package/src/SelectableCard/SelectableCard.doc.mjs +15 -0
- package/src/Selector/Selector.doc.mjs +4 -0
- package/src/Skeleton/Skeleton.doc.mjs +11 -1
- package/src/Slider/Slider.doc.mjs +4 -0
- package/src/Slider/Slider.test.tsx +3 -16
- package/src/Slider/Slider.tsx +1 -1
- package/src/TabList/TabList.doc.mjs +6 -0
- package/src/Table/BaseTable.tsx +72 -41
- package/src/Table/Table.doc.mjs +12 -0
- package/src/Table/Table.test.tsx +29 -30
- package/src/Table/Table.tsx +22 -1
- package/src/Table/TableCell.tsx +25 -23
- package/src/Table/TableHeaderCell.tsx +28 -29
- package/src/Table/TableRow.tsx +37 -7
- package/src/Table/index.ts +6 -0
- package/src/Table/plugins/sortable/useTableSortable.test.tsx +91 -2
- package/src/Table/plugins/sortable/useTableSortable.tsx +53 -1
- package/src/Table/plugins/stickyColumns/index.ts +4 -0
- package/src/Table/plugins/stickyColumns/useTableStickyColumns.test.tsx +163 -0
- package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +407 -0
- package/src/Table/tableContextMenu.test.tsx +157 -0
- package/src/Table/tableContextMenu.tsx +146 -0
- package/src/Table/types.ts +175 -4
- package/src/Table/useBaseTablePlugins.ts +1 -0
- package/src/Table/useTableColumnResize.doc.mjs +96 -0
- package/src/Table/useTableStickyColumns.doc.mjs +59 -0
- package/src/Text/Heading.doc.mjs +15 -2
- package/src/Text/Text.doc.mjs +7 -1
- package/src/Text/text.stylex.ts +2 -2
- package/src/Timestamp/Timestamp.doc.mjs +9 -0
- package/src/Timestamp/Timestamp.test.tsx +44 -18
- package/src/Timestamp/Timestamp.tsx +30 -10
- package/src/ToggleButton/ToggleButton.doc.mjs +9 -3
- package/src/ToggleButton/ToggleButton.test.tsx +39 -18
- package/src/ToggleButton/ToggleButton.tsx +46 -83
- package/src/Tokenizer/Tokenizer.tsx +4 -1
- package/src/Toolbar/Toolbar.doc.mjs +65 -5
- package/src/Tooltip/Tooltip.test.tsx +13 -0
- package/src/Tooltip/useTooltip.tsx +2 -3
- package/src/hooks/useImageMode.test.ts +133 -0
- package/src/theme/derivedVarRegistry.test.ts +4 -0
- package/src/theme/generateThemeRules.test.ts +7 -8
- package/src/theme/generateThemeRules.ts +1 -1
- package/src/theme/syntax/SyntaxTheme.tsx +1 -1
- package/src/theme/syntax/defineSyntaxTheme.ts +20 -11
- package/src/theme/syntax/presets.ts +5 -2
- package/src/theme/syntax/tokens.ts +1 -1
- package/src/theme/types.ts +1 -1
- package/src/utils/dateTypes.test.ts +40 -0
- package/src/utils/dateTypes.ts +40 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTableSortable.d.ts","sourceRoot":"","sources":["../../../../src/Table/plugins/sortable/useTableSortable.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useTableSortable.d.ts","sourceRoot":"","sources":["../../../../src/Table/plugins/sortable/useTableSortable.tsx"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EACV,WAAW,EAIZ,MAAM,aAAa,CAAC;AAMrB;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG,YAAY,CAAC;AAE5D;;GAEG;AACH,MAAM,WAAW,cAAc,CAAC,QAAQ,SAAS,MAAM,GAAG,MAAM;IAC9D,2EAA2E;IAC3E,OAAO,EAAE,QAAQ,CAAC;IAClB,0BAA0B;IAC1B,SAAS,EAAE,kBAAkB,CAAC;CAC/B;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,cAAc,CAAC,QAAQ,SAAS,MAAM,GAAG,MAAM,IACzD,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC;AAM7B;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,sBAAsB,CAAC,QAAQ,SAAS,MAAM,GAAG,MAAM;IACtE,iEAAiE;IACjE,IAAI,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAC;IAE/B;;;OAGG;IACH,YAAY,EAAE,CAAC,IAAI,EAAE,cAAc,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;IAEvD;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAmND;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gBAAgB,CAC9B,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,QAAQ,SAAS,MAAM,GAAG,MAAM,EAChC,MAAM,EAAE,sBAAsB,CAAC,QAAQ,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAyF1D"}
|
|
@@ -17,6 +17,7 @@ import * as stylex from '@stylexjs/stylex';
|
|
|
17
17
|
import "../../../theme/tokens.stylex.js";
|
|
18
18
|
import { colorVars, spacingVars, radiusVars } from "../../../theme/tokens.stylex.js";
|
|
19
19
|
import { Icon } from "../../../Icon/index.js";
|
|
20
|
+
import { resolveContextActions } from "../../tableContextMenu.js";
|
|
20
21
|
|
|
21
22
|
// =============================================================================
|
|
22
23
|
// Sort Types
|
|
@@ -243,10 +244,64 @@ export function useTableSortable(config) {
|
|
|
243
244
|
if (sortKey == null) {
|
|
244
245
|
return props;
|
|
245
246
|
}
|
|
246
|
-
|
|
247
|
-
// Find sort entry for this column
|
|
248
247
|
const cfg = configRef.current;
|
|
249
248
|
const entry = cfg.sort.find(e => e.sortKey === sortKey);
|
|
249
|
+
|
|
250
|
+
// Context-menu actions are built lazily (only when the menu opens) via
|
|
251
|
+
// a getter, reading the current sort state at call time — so we don't
|
|
252
|
+
// build an action array for every header on every render, and the
|
|
253
|
+
// checked/clear state always reflects the latest sort.
|
|
254
|
+
const getSortActions = () => {
|
|
255
|
+
const c = configRef.current;
|
|
256
|
+
const dir = c.sort.find(e => e.sortKey === sortKey)?.direction ?? null;
|
|
257
|
+
const actions = [{
|
|
258
|
+
id: 'sort-asc',
|
|
259
|
+
group: 'sort',
|
|
260
|
+
label: 'Sort ascending',
|
|
261
|
+
icon: /*#__PURE__*/_jsx(Icon, {
|
|
262
|
+
icon: "arrowUp",
|
|
263
|
+
size: "xsm",
|
|
264
|
+
"aria-hidden": true
|
|
265
|
+
}),
|
|
266
|
+
checked: dir === 'ascending',
|
|
267
|
+
onSelect: () => c.onSortChange([{
|
|
268
|
+
sortKey: sortKey,
|
|
269
|
+
direction: 'ascending'
|
|
270
|
+
}])
|
|
271
|
+
}, {
|
|
272
|
+
id: 'sort-desc',
|
|
273
|
+
group: 'sort',
|
|
274
|
+
label: 'Sort descending',
|
|
275
|
+
icon: /*#__PURE__*/_jsx(Icon, {
|
|
276
|
+
icon: "arrowDown",
|
|
277
|
+
size: "xsm",
|
|
278
|
+
"aria-hidden": true
|
|
279
|
+
}),
|
|
280
|
+
checked: dir === 'descending',
|
|
281
|
+
onSelect: () => c.onSortChange([{
|
|
282
|
+
sortKey: sortKey,
|
|
283
|
+
direction: 'descending'
|
|
284
|
+
}])
|
|
285
|
+
}];
|
|
286
|
+
if (dir != null) {
|
|
287
|
+
actions.push({
|
|
288
|
+
id: 'sort-clear',
|
|
289
|
+
group: 'sort-clear',
|
|
290
|
+
label: 'Clear sort',
|
|
291
|
+
icon: /*#__PURE__*/_jsx(Icon, {
|
|
292
|
+
icon: "close",
|
|
293
|
+
size: "xsm",
|
|
294
|
+
"aria-hidden": true
|
|
295
|
+
}),
|
|
296
|
+
onSelect: () => c.onSortChange(c.sort.filter(e => e.sortKey !== sortKey))
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
return actions;
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// Merge with any actions a prior plugin contributed (array or getter),
|
|
303
|
+
// resolving both lazily at open time.
|
|
304
|
+
const priorActions = props.contextMenuActions;
|
|
250
305
|
return {
|
|
251
306
|
...props,
|
|
252
307
|
htmlProps: {
|
|
@@ -259,7 +314,8 @@ export function useTableSortable(config) {
|
|
|
259
314
|
column: column,
|
|
260
315
|
configRef: configRef,
|
|
261
316
|
children: props.content
|
|
262
|
-
})
|
|
317
|
+
}),
|
|
318
|
+
contextMenuActions: () => [...resolveContextActions(priorActions), ...getSortActions()]
|
|
263
319
|
};
|
|
264
320
|
}
|
|
265
321
|
}), []);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/Table/plugins/stickyColumns/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,qBAAqB,EAAC,MAAM,yBAAyB,CAAC;AAC9D,YAAY,EAAC,2BAA2B,EAAC,MAAM,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { TablePlugin } from '../../types';
|
|
2
|
+
/**
|
|
3
|
+
* Config for {@link useTableStickyColumns}. Provide at least one of
|
|
4
|
+
* `startKeys` / `endKeys` to pin columns.
|
|
5
|
+
*
|
|
6
|
+
* @remarks Every field is optional by design, so `useTableStickyColumns({})`
|
|
7
|
+
* compiles and is an intentional no-op that pins nothing — the hook returns a
|
|
8
|
+
* plugin that passes every cell through untouched. This lets callers compute
|
|
9
|
+
* the config conditionally (e.g. `endKeys: enabled ? ['notes'] : undefined`)
|
|
10
|
+
* without branching on whether to install the plugin at all.
|
|
11
|
+
*/
|
|
12
|
+
export interface UseTableStickyColumnsConfig {
|
|
13
|
+
/**
|
|
14
|
+
* Column keys pinned to the START (inline-start / left in LTR) edge — the
|
|
15
|
+
* contiguous run from the first column through the last listed key.
|
|
16
|
+
*/
|
|
17
|
+
startKeys?: string[];
|
|
18
|
+
/**
|
|
19
|
+
* Column keys pinned to the END (inline-end / right in LTR) edge — the
|
|
20
|
+
* contiguous run from the first listed key through the last column.
|
|
21
|
+
*/
|
|
22
|
+
endKeys?: string[];
|
|
23
|
+
}
|
|
24
|
+
export declare function useTableStickyColumns<T extends Record<string, unknown>>(config: UseTableStickyColumnsConfig): TablePlugin<T>;
|
|
25
|
+
//# sourceMappingURL=useTableStickyColumns.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTableStickyColumns.d.ts","sourceRoot":"","sources":["../../../../src/Table/plugins/stickyColumns/useTableStickyColumns.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAEV,WAAW,EAIZ,MAAM,aAAa,CAAC;AAOrB;;;;;;;;;GASG;AACH,MAAM,WAAW,2BAA2B;IAC1C;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AA6MD,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrE,MAAM,EAAE,2BAA2B,GAClC,WAAW,CAAC,CAAC,CAAC,CAmJhB"}
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file useTableStickyColumns.tsx
|
|
7
|
+
* @input React, StyleX, theme tokens, Table types
|
|
8
|
+
* @output Exports useTableStickyColumns hook and UseTableStickyColumnsConfig type
|
|
9
|
+
* @position Sticky-columns plugin; consumed by Table via plugins prop
|
|
10
|
+
*
|
|
11
|
+
* SYNC: When modified, update these files to stay in sync:
|
|
12
|
+
* - /packages/core/src/Table/Table.doc.mjs (sticky-columns documentation)
|
|
13
|
+
* - /packages/core/src/Table/index.ts (exports)
|
|
14
|
+
*/
|
|
15
|
+
import { useCallback, useMemo, useRef } from 'react';
|
|
16
|
+
import * as stylex from '@stylexjs/stylex';
|
|
17
|
+
import "../../../theme/tokens.stylex.js";
|
|
18
|
+
import { colorVars, durationVars, easeVars } from "../../../theme/tokens.stylex.js";
|
|
19
|
+
import { DEFAULT_MIN_COLUMN_WIDTH } from "../../columnUtils.js";
|
|
20
|
+
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// Config
|
|
23
|
+
// =============================================================================
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Config for {@link useTableStickyColumns}. Provide at least one of
|
|
27
|
+
* `startKeys` / `endKeys` to pin columns.
|
|
28
|
+
*
|
|
29
|
+
* @remarks Every field is optional by design, so `useTableStickyColumns({})`
|
|
30
|
+
* compiles and is an intentional no-op that pins nothing — the hook returns a
|
|
31
|
+
* plugin that passes every cell through untouched. This lets callers compute
|
|
32
|
+
* the config conditionally (e.g. `endKeys: enabled ? ['notes'] : undefined`)
|
|
33
|
+
* without branching on whether to install the plugin at all.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
// =============================================================================
|
|
37
|
+
// Width helpers
|
|
38
|
+
// =============================================================================
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Resolve a column's pixel width for cumulative offset math. Mirrors the
|
|
42
|
+
* resize plugin's fallback so offsets line up with rendered widths:
|
|
43
|
+
* pixel columns use their value; proportional columns use their declared
|
|
44
|
+
* minWidth (or the default); unknown widths use the default.
|
|
45
|
+
*/
|
|
46
|
+
function getColumnWidth(col) {
|
|
47
|
+
const w = col.width;
|
|
48
|
+
if (!w) {
|
|
49
|
+
return DEFAULT_MIN_COLUMN_WIDTH;
|
|
50
|
+
}
|
|
51
|
+
if (w.type === 'pixel') {
|
|
52
|
+
return w.value;
|
|
53
|
+
}
|
|
54
|
+
// proportional
|
|
55
|
+
return w.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Columns pinned to the START edge → cumulative inline offset (px). The pinned
|
|
60
|
+
* block is the contiguous run from column 0 through the last key in `startKeys`
|
|
61
|
+
* (inclusive of any synthetic columns before it). `null` if none / no context.
|
|
62
|
+
*/
|
|
63
|
+
function computeStartOffsets(columns, startKeys) {
|
|
64
|
+
if (!columns || columns.length === 0 || startKeys.length === 0) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
let lastStickyIndex = -1;
|
|
68
|
+
for (let i = 0; i < columns.length; i++) {
|
|
69
|
+
if (startKeys.includes(columns[i].key)) {
|
|
70
|
+
lastStickyIndex = i;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (lastStickyIndex === -1) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const offsets = new Map();
|
|
77
|
+
let cumulative = 0;
|
|
78
|
+
for (let i = 0; i <= lastStickyIndex; i++) {
|
|
79
|
+
offsets.set(columns[i].key, cumulative);
|
|
80
|
+
cumulative += getColumnWidth(columns[i]);
|
|
81
|
+
}
|
|
82
|
+
return offsets;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Mirror of {@link computeStartOffsets} for the END edge — contiguous run from
|
|
87
|
+
* the first key in `endKeys` through the last column, offsets accumulating from
|
|
88
|
+
* the end (last column = 0). `null` if none.
|
|
89
|
+
*/
|
|
90
|
+
function computeEndOffsets(columns, endKeys) {
|
|
91
|
+
if (!columns || columns.length === 0 || endKeys.length === 0) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
let firstStickyIndex = -1;
|
|
95
|
+
for (let i = 0; i < columns.length; i++) {
|
|
96
|
+
if (endKeys.includes(columns[i].key)) {
|
|
97
|
+
firstStickyIndex = i;
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (firstStickyIndex === -1) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
const offsets = new Map();
|
|
105
|
+
let cumulative = 0;
|
|
106
|
+
for (let i = columns.length - 1; i >= firstStickyIndex; i--) {
|
|
107
|
+
offsets.set(columns[i].key, cumulative);
|
|
108
|
+
cumulative += getColumnWidth(columns[i]);
|
|
109
|
+
}
|
|
110
|
+
return offsets;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Resolve how a single column should be pinned given the start/end configs and
|
|
114
|
+
* the full column list. A key (mis)configured on both edges resolves to start.
|
|
115
|
+
* Returns `null` for columns that should not be pinned.
|
|
116
|
+
*/
|
|
117
|
+
function resolveStickySide(columns, columnKey, startKeys, endKeys) {
|
|
118
|
+
const startOffsets = computeStartOffsets(columns, startKeys);
|
|
119
|
+
if (startOffsets?.has(columnKey)) {
|
|
120
|
+
return {
|
|
121
|
+
edge: 'start',
|
|
122
|
+
offset: startOffsets.get(columnKey) ?? 0
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
const endOffsets = computeEndOffsets(columns, endKeys);
|
|
126
|
+
if (endOffsets?.has(columnKey)) {
|
|
127
|
+
return {
|
|
128
|
+
edge: 'end',
|
|
129
|
+
offset: endOffsets.get(columnKey) ?? 0
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// =============================================================================
|
|
136
|
+
// Styles
|
|
137
|
+
// =============================================================================
|
|
138
|
+
|
|
139
|
+
// Scroll-state CSS variables set on the scroll container by the layout ref;
|
|
140
|
+
// the cell ::after shadows read them so each edge's shadow only shows when
|
|
141
|
+
// content is hidden behind it. (A variable, not stylex.when.ancestor, because
|
|
142
|
+
// StyleX ancestor selectors are pseudo-class only — not scroll state.)
|
|
143
|
+
const SHADOW_VAR_START = '--table-sticky-shadow-start';
|
|
144
|
+
const SHADOW_VAR_END = '--table-sticky-shadow-end';
|
|
145
|
+
const stickyStyles = {
|
|
146
|
+
cell: {
|
|
147
|
+
kVAEAm: "x7wzq59",
|
|
148
|
+
kWkggS: "x1fd97le",
|
|
149
|
+
kgeoSG: "x1cpjm7i",
|
|
150
|
+
kEoFBp: "x1hmns74",
|
|
151
|
+
kFcpXp: "xxx281p",
|
|
152
|
+
kxmVGU: "xy5mcqj",
|
|
153
|
+
km8f2m: "xkk1bqk",
|
|
154
|
+
kLkRvE: "x1uvfo0n",
|
|
155
|
+
k7KCAZ: "xxcwgru",
|
|
156
|
+
kKXxxB: "x1y1nw7a",
|
|
157
|
+
kqUdNP: "xa5v58t",
|
|
158
|
+
kHypHr: "xx83zyx",
|
|
159
|
+
kVQacm: "x1rea2x4",
|
|
160
|
+
$$css: true
|
|
161
|
+
},
|
|
162
|
+
headerCell: {
|
|
163
|
+
kY2c9j: "xzkaem6",
|
|
164
|
+
$$css: true
|
|
165
|
+
},
|
|
166
|
+
bodyCell: {
|
|
167
|
+
kY2c9j: "x1vjfegm",
|
|
168
|
+
$$css: true
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// Soft drop shadow over the scrolling region (matches the EPS sticky treatment).
|
|
173
|
+
// border-collapse tables don't paint box-shadow on cells in Chromium, so it's a
|
|
174
|
+
// ::after gradient strip just past the pinned edge (visible thanks to the cell's
|
|
175
|
+
// overflow: visible). Opacity is gated by the scroll-state variable above.
|
|
176
|
+
const SHADOW_WIDTH = '6px';
|
|
177
|
+
// --color-shadow is only ~10% alpha (too faint here); use a slightly stronger soft tint.
|
|
178
|
+
const SHADOW_TINT = 'light-dark(rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.32))';
|
|
179
|
+
const shadowStyles = {
|
|
180
|
+
start: {
|
|
181
|
+
k5JduY: "x1s928wv",
|
|
182
|
+
kwXMNM: "x1j6awrg",
|
|
183
|
+
k3foIR: "x1m1drc7",
|
|
184
|
+
k8Iv0R: "x1xrz1ek",
|
|
185
|
+
kH8aOt: "x1unh1gc",
|
|
186
|
+
kkgrvl: "xzkji8o",
|
|
187
|
+
kLigFv: "x1qyefdi",
|
|
188
|
+
kloYau: "x2q1x1w",
|
|
189
|
+
kFJxch: "x17s1k9h",
|
|
190
|
+
kypkao: "x1sggmfs",
|
|
191
|
+
kNctxI: "x156sm4c",
|
|
192
|
+
$$css: true
|
|
193
|
+
},
|
|
194
|
+
end: {
|
|
195
|
+
k5JduY: "x1s928wv",
|
|
196
|
+
kwXMNM: "x1j6awrg",
|
|
197
|
+
k3foIR: "x1m1drc7",
|
|
198
|
+
k8Iv0R: "x1xrz1ek",
|
|
199
|
+
kc1e00: "x1iygr5g",
|
|
200
|
+
kkgrvl: "xzkji8o",
|
|
201
|
+
kLigFv: "xvs0bi2",
|
|
202
|
+
kloYau: "x2q1x1w",
|
|
203
|
+
kFJxch: "x17s1k9h",
|
|
204
|
+
kypkao: "x1c2idit",
|
|
205
|
+
kNctxI: "x14ofgck",
|
|
206
|
+
$$css: true
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
// Stable empty default so an unset start/end doesn't allocate a fresh array
|
|
211
|
+
// (and bust the memo) on every render.
|
|
212
|
+
const EMPTY = [];
|
|
213
|
+
|
|
214
|
+
// =============================================================================
|
|
215
|
+
// Hook
|
|
216
|
+
// =============================================================================
|
|
217
|
+
|
|
218
|
+
export function useTableStickyColumns(config) {
|
|
219
|
+
const {
|
|
220
|
+
startKeys,
|
|
221
|
+
endKeys
|
|
222
|
+
} = config;
|
|
223
|
+
const start = startKeys ?? EMPTY;
|
|
224
|
+
const end = endKeys ?? EMPTY;
|
|
225
|
+
const hasStart = start.length > 0;
|
|
226
|
+
const hasEnd = end.length > 0;
|
|
227
|
+
|
|
228
|
+
// Live snapshot of the resolved config, read inside the (memoized) transforms
|
|
229
|
+
// and the scroll-shadow callback. Keeping these in a ref lets the plugin memo
|
|
230
|
+
// be computed once (deps: only the stable scroll-shadow callback) while never
|
|
231
|
+
// reading stale config — consumers typically pass fresh array literals each
|
|
232
|
+
// render, so we never want array identity in the deps.
|
|
233
|
+
const stateRef = useRef({
|
|
234
|
+
start,
|
|
235
|
+
end,
|
|
236
|
+
hasStart,
|
|
237
|
+
hasEnd
|
|
238
|
+
});
|
|
239
|
+
stateRef.current = {
|
|
240
|
+
start,
|
|
241
|
+
end,
|
|
242
|
+
hasStart,
|
|
243
|
+
hasEnd
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// Scroll-aware shadows: toggle CSS variables on the scroll container so each
|
|
247
|
+
// edge's shadow only paints when there is hidden, horizontally-scrolled
|
|
248
|
+
// content behind that edge. Implemented with a callback ref + scroll/resize
|
|
249
|
+
// listeners (synchronizing with the DOM) rather than React state, so
|
|
250
|
+
// scrolling never triggers re-renders.
|
|
251
|
+
const detachRef = useRef(null);
|
|
252
|
+
const attachScrollShadow = useCallback(el => {
|
|
253
|
+
detachRef.current?.();
|
|
254
|
+
detachRef.current = null;
|
|
255
|
+
if (!el) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const update = () => {
|
|
259
|
+
const {
|
|
260
|
+
hasStart: hs,
|
|
261
|
+
hasEnd: he
|
|
262
|
+
} = stateRef.current;
|
|
263
|
+
const maxScroll = el.scrollWidth - el.clientWidth;
|
|
264
|
+
const hasOverflow = maxScroll > 1;
|
|
265
|
+
if (hs) {
|
|
266
|
+
el.style.setProperty(SHADOW_VAR_START, hasOverflow && el.scrollLeft > 1 ? '1' : '0');
|
|
267
|
+
}
|
|
268
|
+
if (he) {
|
|
269
|
+
el.style.setProperty(SHADOW_VAR_END, hasOverflow && el.scrollLeft < maxScroll - 1 ? '1' : '0');
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
el.addEventListener('scroll', update, {
|
|
273
|
+
passive: true
|
|
274
|
+
});
|
|
275
|
+
const resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(update) : null;
|
|
276
|
+
resizeObserver?.observe(el);
|
|
277
|
+
update();
|
|
278
|
+
detachRef.current = () => {
|
|
279
|
+
el.removeEventListener('scroll', update);
|
|
280
|
+
resizeObserver?.disconnect();
|
|
281
|
+
};
|
|
282
|
+
}, []);
|
|
283
|
+
return useMemo(() => ({
|
|
284
|
+
transformHeaderCell(props, column) {
|
|
285
|
+
const {
|
|
286
|
+
start: s,
|
|
287
|
+
end: e
|
|
288
|
+
} = stateRef.current;
|
|
289
|
+
const side = resolveStickySide(props.columns, column.key, s, e);
|
|
290
|
+
if (!side) {
|
|
291
|
+
return props;
|
|
292
|
+
}
|
|
293
|
+
// position/inline-offset are runtime values → set via inline style so
|
|
294
|
+
// they are authoritative regardless of plugin composition order (the
|
|
295
|
+
// resize plugin also writes inline style on header cells).
|
|
296
|
+
const offsetStyle = side.edge === 'start' ? {
|
|
297
|
+
insetInlineStart: `${side.offset}px`
|
|
298
|
+
} : {
|
|
299
|
+
insetInlineEnd: `${side.offset}px`
|
|
300
|
+
};
|
|
301
|
+
return {
|
|
302
|
+
...props,
|
|
303
|
+
htmlProps: {
|
|
304
|
+
...props.htmlProps,
|
|
305
|
+
style: {
|
|
306
|
+
...props.htmlProps.style,
|
|
307
|
+
...offsetStyle
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
styles: [...props.styles, stickyStyles.cell, stickyStyles.headerCell, side.edge === 'start' ? shadowStyles.start : shadowStyles.end]
|
|
311
|
+
};
|
|
312
|
+
},
|
|
313
|
+
transformBodyCell(props, column) {
|
|
314
|
+
const {
|
|
315
|
+
start: s,
|
|
316
|
+
end: e
|
|
317
|
+
} = stateRef.current;
|
|
318
|
+
const side = resolveStickySide(props.columns, column.key, s, e);
|
|
319
|
+
if (!side) {
|
|
320
|
+
return props;
|
|
321
|
+
}
|
|
322
|
+
const offsetStyle = side.edge === 'start' ? {
|
|
323
|
+
insetInlineStart: `${side.offset}px`
|
|
324
|
+
} : {
|
|
325
|
+
insetInlineEnd: `${side.offset}px`
|
|
326
|
+
};
|
|
327
|
+
return {
|
|
328
|
+
...props,
|
|
329
|
+
htmlProps: {
|
|
330
|
+
...props.htmlProps,
|
|
331
|
+
style: {
|
|
332
|
+
...props.htmlProps.style,
|
|
333
|
+
...offsetStyle
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
styles: [...props.styles, stickyStyles.cell, stickyStyles.bodyCell, side.edge === 'start' ? shadowStyles.start : shadowStyles.end]
|
|
337
|
+
};
|
|
338
|
+
},
|
|
339
|
+
transformScrollWrapper(props) {
|
|
340
|
+
// No pinned edges → nothing to gate; leave the wrapper untouched.
|
|
341
|
+
if (!stateRef.current.hasStart && !stateRef.current.hasEnd) {
|
|
342
|
+
return props;
|
|
343
|
+
}
|
|
344
|
+
// Compose with any ref a prior plugin (e.g. virtualization) set on the
|
|
345
|
+
// scroll container.
|
|
346
|
+
const existingRef = props.htmlProps.ref;
|
|
347
|
+
const mergedRef = node => {
|
|
348
|
+
attachScrollShadow(node);
|
|
349
|
+
if (typeof existingRef === 'function') {
|
|
350
|
+
existingRef(node);
|
|
351
|
+
} else if (existingRef != null) {
|
|
352
|
+
// RefObject — assign through its writable `.current`.
|
|
353
|
+
existingRef.current = node;
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
return {
|
|
357
|
+
...props,
|
|
358
|
+
htmlProps: {
|
|
359
|
+
...props.htmlProps,
|
|
360
|
+
ref: mergedRef
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
}),
|
|
365
|
+
// The returned plugin's transforms read live config from `stateRef` and
|
|
366
|
+
// `attachScrollShadow` is stable (empty deps), so the plugin object can be
|
|
367
|
+
// computed once and reused across renders.
|
|
368
|
+
[attachScrollShadow]);
|
|
369
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file tableContextMenu.tsx
|
|
3
|
+
* @input React, ContextMenu, Icon, types
|
|
4
|
+
* @output wrapInTableContextMenu helper
|
|
5
|
+
* @position Renders the aggregated `contextMenuActions` for a header cell / row
|
|
6
|
+
*
|
|
7
|
+
* SYNC: When modified, update these files to stay in sync:
|
|
8
|
+
* - /packages/core/src/Table/BaseTable.tsx (call sites)
|
|
9
|
+
* - /packages/core/src/Table/types.ts (TableContextAction)
|
|
10
|
+
*/
|
|
11
|
+
import { type ReactNode } from 'react';
|
|
12
|
+
import type { TableContextAction, TableContextActions } from './types';
|
|
13
|
+
/**
|
|
14
|
+
* Resolve a `contextMenuActions` value (array or getter) to a plain array.
|
|
15
|
+
* Use this when composing actions from a prior plugin's render props so you
|
|
16
|
+
* don't have to branch on the array-vs-getter form:
|
|
17
|
+
*
|
|
18
|
+
* ```
|
|
19
|
+
* contextMenuActions: () => [
|
|
20
|
+
* ...resolveContextActions(props.contextMenuActions),
|
|
21
|
+
* ...myActions,
|
|
22
|
+
* ]
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveContextActions(actions: TableContextActions | undefined): TableContextAction[];
|
|
26
|
+
/**
|
|
27
|
+
* Wrap a table element (a header cell's content or a body cell) in a right-click
|
|
28
|
+
* context menu rendering the aggregated `actions`. Accepts a static array or a
|
|
29
|
+
* getter (resolved lazily on open). When there are no actions the element is
|
|
30
|
+
* returned untouched so the native browser menu passes through.
|
|
31
|
+
*/
|
|
32
|
+
export declare function wrapInTableContextMenu(element: ReactNode, actions: TableContextActions | undefined): ReactNode;
|
|
33
|
+
//# sourceMappingURL=tableContextMenu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tableContextMenu.d.ts","sourceRoot":"","sources":["../../src/Table/tableContextMenu.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AAEH,OAAO,EAAW,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAG/C,OAAO,KAAK,EAAC,kBAAkB,EAAE,mBAAmB,EAAC,MAAM,SAAS,CAAC;AAErE;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,mBAAmB,GAAG,SAAS,GACvC,kBAAkB,EAAE,CAKtB;AAiFD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,SAAS,EAClB,OAAO,EAAE,mBAAmB,GAAG,SAAS,GACvC,SAAS,CAgBX"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file tableContextMenu.tsx
|
|
7
|
+
* @input React, ContextMenu, Icon, types
|
|
8
|
+
* @output wrapInTableContextMenu helper
|
|
9
|
+
* @position Renders the aggregated `contextMenuActions` for a header cell / row
|
|
10
|
+
*
|
|
11
|
+
* SYNC: When modified, update these files to stay in sync:
|
|
12
|
+
* - /packages/core/src/Table/BaseTable.tsx (call sites)
|
|
13
|
+
* - /packages/core/src/Table/types.ts (TableContextAction)
|
|
14
|
+
*/
|
|
15
|
+
import { useState } from 'react';
|
|
16
|
+
import { ContextMenu } from "../ContextMenu/index.js";
|
|
17
|
+
import { Icon } from "../Icon/index.js";
|
|
18
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
19
|
+
/**
|
|
20
|
+
* Resolve a `contextMenuActions` value (array or getter) to a plain array.
|
|
21
|
+
* Use this when composing actions from a prior plugin's render props so you
|
|
22
|
+
* don't have to branch on the array-vs-getter form:
|
|
23
|
+
*
|
|
24
|
+
* ```
|
|
25
|
+
* contextMenuActions: () => [
|
|
26
|
+
* ...resolveContextActions(props.contextMenuActions),
|
|
27
|
+
* ...myActions,
|
|
28
|
+
* ]
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function resolveContextActions(actions) {
|
|
32
|
+
if (typeof actions === 'function') {
|
|
33
|
+
return actions();
|
|
34
|
+
}
|
|
35
|
+
return actions ?? [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Convert the flat action list into ContextMenu options, inserting a divider
|
|
40
|
+
* between groups (first-seen group order). Ungrouped actions form a trailing
|
|
41
|
+
* group. A `checked` action shows a trailing check icon.
|
|
42
|
+
*/
|
|
43
|
+
function toContextMenuOptions(actions) {
|
|
44
|
+
const order = [];
|
|
45
|
+
const buckets = new Map();
|
|
46
|
+
for (const action of actions) {
|
|
47
|
+
const key = action.group ?? '__ungrouped__';
|
|
48
|
+
let bucket = buckets.get(key);
|
|
49
|
+
if (!bucket) {
|
|
50
|
+
bucket = [];
|
|
51
|
+
buckets.set(key, bucket);
|
|
52
|
+
order.push(key);
|
|
53
|
+
}
|
|
54
|
+
bucket.push(action);
|
|
55
|
+
}
|
|
56
|
+
const options = [];
|
|
57
|
+
order.forEach((key, groupIndex) => {
|
|
58
|
+
if (groupIndex > 0) {
|
|
59
|
+
options.push({
|
|
60
|
+
type: 'divider'
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
for (const action of buckets.get(key) ?? []) {
|
|
64
|
+
options.push({
|
|
65
|
+
label: typeof action.label === 'string' ? action.label : action.id,
|
|
66
|
+
// A checked action (e.g. the active sort direction) shows a checkmark;
|
|
67
|
+
// otherwise the action's own icon. ContextMenu's data form takes a
|
|
68
|
+
// single leading icon, so checked state replaces the icon.
|
|
69
|
+
icon: action.checked ? /*#__PURE__*/_jsx(Icon, {
|
|
70
|
+
icon: "check",
|
|
71
|
+
size: "xsm",
|
|
72
|
+
"aria-hidden": true
|
|
73
|
+
}) : action.icon,
|
|
74
|
+
isDisabled: action.disabled,
|
|
75
|
+
onClick: action.onSelect
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
return options;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Wrap a table element (a header cell's content or a row) in a right-click
|
|
84
|
+
* context menu rendering the aggregated `actions`. When no plugin contributes
|
|
85
|
+
* actions the element is returned untouched so the native browser menu passes
|
|
86
|
+
* through.
|
|
87
|
+
*/
|
|
88
|
+
/**
|
|
89
|
+
* A context menu whose actions are resolved lazily — only when the user opens
|
|
90
|
+
* the menu (right-click). Deferring the work means plugins that pass a getter
|
|
91
|
+
* don't build an action array (with its closures) for every cell on every
|
|
92
|
+
* render; it's computed on demand and memoized until the menu closes.
|
|
93
|
+
*/
|
|
94
|
+
function LazyTableContextMenu({
|
|
95
|
+
element,
|
|
96
|
+
getActions
|
|
97
|
+
}) {
|
|
98
|
+
const [options, setOptions] = useState(null);
|
|
99
|
+
return /*#__PURE__*/_jsx(ContextMenu, {
|
|
100
|
+
items: options ?? [],
|
|
101
|
+
hasAutoFocus: false,
|
|
102
|
+
onOpenChange: open => {
|
|
103
|
+
// Resolve actions when opening; clear on close so state derived later
|
|
104
|
+
// (e.g. current sort direction) is always fresh next open.
|
|
105
|
+
setOptions(open ? toContextMenuOptions(getActions()) : null);
|
|
106
|
+
},
|
|
107
|
+
children: element
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Wrap a table element (a header cell's content or a body cell) in a right-click
|
|
113
|
+
* context menu rendering the aggregated `actions`. Accepts a static array or a
|
|
114
|
+
* getter (resolved lazily on open). When there are no actions the element is
|
|
115
|
+
* returned untouched so the native browser menu passes through.
|
|
116
|
+
*/
|
|
117
|
+
export function wrapInTableContextMenu(element, actions) {
|
|
118
|
+
// Getter form → resolve lazily on open.
|
|
119
|
+
if (typeof actions === 'function') {
|
|
120
|
+
return /*#__PURE__*/_jsx(LazyTableContextMenu, {
|
|
121
|
+
element: element,
|
|
122
|
+
getActions: actions
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (!actions || actions.length === 0) {
|
|
126
|
+
return element;
|
|
127
|
+
}
|
|
128
|
+
// hasAutoFocus={false}: a right-click menu shouldn't pre-highlight the first
|
|
129
|
+
// item (it looked like "ascending" was always selected). Focus moves only
|
|
130
|
+
// when the user arrow-keys into the menu.
|
|
131
|
+
return /*#__PURE__*/_jsx(ContextMenu, {
|
|
132
|
+
items: toContextMenuOptions(actions),
|
|
133
|
+
hasAutoFocus: false,
|
|
134
|
+
children: element
|
|
135
|
+
});
|
|
136
|
+
}
|