@prairielearn/ui 1.2.0 → 1.4.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.
Files changed (68) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +4 -2
  3. package/dist/components/CategoricalColumnFilter.d.ts +7 -12
  4. package/dist/components/CategoricalColumnFilter.d.ts.map +1 -1
  5. package/dist/components/CategoricalColumnFilter.js +26 -14
  6. package/dist/components/CategoricalColumnFilter.js.map +1 -1
  7. package/dist/components/ColumnManager.d.ts +6 -2
  8. package/dist/components/ColumnManager.d.ts.map +1 -1
  9. package/dist/components/ColumnManager.js +98 -35
  10. package/dist/components/ColumnManager.js.map +1 -1
  11. package/dist/components/MultiSelectColumnFilter.d.ts +8 -12
  12. package/dist/components/MultiSelectColumnFilter.d.ts.map +1 -1
  13. package/dist/components/MultiSelectColumnFilter.js +21 -13
  14. package/dist/components/MultiSelectColumnFilter.js.map +1 -1
  15. package/dist/components/NumericInputColumnFilter.d.ts +13 -13
  16. package/dist/components/NumericInputColumnFilter.d.ts.map +1 -1
  17. package/dist/components/NumericInputColumnFilter.js +44 -15
  18. package/dist/components/NumericInputColumnFilter.js.map +1 -1
  19. package/dist/components/NumericInputColumnFilter.test.d.ts +2 -0
  20. package/dist/components/NumericInputColumnFilter.test.d.ts.map +1 -0
  21. package/dist/components/NumericInputColumnFilter.test.js +90 -0
  22. package/dist/components/NumericInputColumnFilter.test.js.map +1 -0
  23. package/dist/components/OverlayTrigger.d.ts +78 -0
  24. package/dist/components/OverlayTrigger.d.ts.map +1 -0
  25. package/dist/components/OverlayTrigger.js +89 -0
  26. package/dist/components/OverlayTrigger.js.map +1 -0
  27. package/dist/components/TanstackTable.d.ts +19 -3
  28. package/dist/components/TanstackTable.d.ts.map +1 -1
  29. package/dist/components/TanstackTable.js +159 -219
  30. package/dist/components/TanstackTable.js.map +1 -1
  31. package/dist/components/TanstackTableDownloadButton.d.ts +4 -2
  32. package/dist/components/TanstackTableDownloadButton.d.ts.map +1 -1
  33. package/dist/components/TanstackTableDownloadButton.js +4 -3
  34. package/dist/components/TanstackTableDownloadButton.js.map +1 -1
  35. package/dist/components/TanstackTableHeaderCell.d.ts +13 -0
  36. package/dist/components/TanstackTableHeaderCell.d.ts.map +1 -0
  37. package/dist/components/TanstackTableHeaderCell.js +98 -0
  38. package/dist/components/TanstackTableHeaderCell.js.map +1 -0
  39. package/dist/components/styles.css +58 -0
  40. package/dist/components/useAutoSizeColumns.d.ts +17 -0
  41. package/dist/components/useAutoSizeColumns.d.ts.map +1 -0
  42. package/dist/components/useAutoSizeColumns.js +99 -0
  43. package/dist/components/useAutoSizeColumns.js.map +1 -0
  44. package/dist/index.d.ts +4 -2
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +4 -1
  47. package/dist/index.js.map +1 -1
  48. package/dist/react-table.d.ts +13 -0
  49. package/dist/react-table.d.ts.map +1 -0
  50. package/dist/react-table.js +3 -0
  51. package/dist/react-table.js.map +1 -0
  52. package/package.json +2 -2
  53. package/src/components/CategoricalColumnFilter.tsx +84 -54
  54. package/src/components/ColumnManager.tsx +236 -88
  55. package/src/components/MultiSelectColumnFilter.tsx +45 -32
  56. package/src/components/NumericInputColumnFilter.test.ts +67 -19
  57. package/src/components/NumericInputColumnFilter.tsx +102 -42
  58. package/src/components/OverlayTrigger.tsx +168 -0
  59. package/src/components/TanstackTable.tsx +357 -410
  60. package/src/components/TanstackTableDownloadButton.tsx +8 -5
  61. package/src/components/TanstackTableHeaderCell.tsx +207 -0
  62. package/src/components/styles.css +58 -0
  63. package/src/components/useAutoSizeColumns.tsx +168 -0
  64. package/src/index.ts +10 -1
  65. package/src/react-table.ts +17 -0
  66. package/tsconfig.json +1 -2
  67. package/dist/components/TanstackTable.css +0 -4
  68. package/src/components/TanstackTable.css +0 -4
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # @prairielearn/ui
2
2
 
3
+ ## 1.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 02db253: Add `OverlayTrigger` component
8
+ - e6c52c9: Namespace UI classes that aren't for direct use, simplify usage
9
+ - 4d11204: Virtualize columns, add useAutoSizeColumns hook, hierarchical display of columns
10
+ - f7d6b62: - Improve options for singular/plural labels
11
+ - Improve UI for column manager / card
12
+ - Add ability to wrap columns
13
+ - Add HTML props to TanstackTableCard
14
+ - Add scrollRef to TanstackTable
15
+ - Add virtualized element measuring to TanstackTable
16
+ - Improve keyboard navigation in TanstackTable
17
+ - cf71f7e: Support for grouped columns in ColumnManager, empty values in NumericInputColumnFilter
18
+ - b6c34cf: Refactor state management of filters
19
+
20
+ ### Patch Changes
21
+
22
+ - Updated dependencies [f7d6b62]
23
+ - @prairielearn/browser-utils@2.6.0
24
+
25
+ ## 1.3.0
26
+
27
+ ### Minor Changes
28
+
29
+ - 3b54dda: Refine styling of `<CategoricalColumnFilter>`
30
+
31
+ ### Patch Changes
32
+
33
+ - 9f5a05f: Improve UI of `TanstackTable` and `TanstackTableCard`
34
+
3
35
  ## 1.2.0
4
36
 
5
37
  ### Minor Changes
package/README.md CHANGED
@@ -14,9 +14,11 @@ import { TanstackTableCard } from '@prairielearn/ui';
14
14
  <TanstackTableCard
15
15
  table={table}
16
16
  title="Students"
17
+ className="h-100"
18
+ singularLabel="student"
19
+ pluralLabel="students"
17
20
  downloadButtonOptions={{
18
21
  filenameBase: `${courseInstanceFilenamePrefix(courseInstance, course)}students`,
19
- singularLabel: 'student',
20
22
  mapRowToData: (row) => {
21
23
  return {
22
24
  uid: row.user?.uid ?? row.enrollment.pending_uid,
@@ -57,7 +59,7 @@ import { TanstackTableCard } from '@prairielearn/ui';
57
59
  You should also include the CSS file in your page:
58
60
 
59
61
  ```css
60
- @import url('@prairielearn/ui/components/TanstackTable.css');
62
+ @import url('@prairielearn/ui/components/styles.css');
61
63
  ```
62
64
 
63
65
  ### CategoricalColumnFilter
@@ -1,25 +1,20 @@
1
+ import type { Column } from '@tanstack/react-table';
1
2
  import { type JSX } from 'preact/compat';
2
3
  /**
3
- * A component that allows the user to filter a categorical column. State is managed by the parent component.
4
+ * A component that allows the user to filter a categorical column.
4
5
  * The filter mode always defaults to "include".
5
6
  *
6
7
  * @param params
7
- * @param params.columnId - The ID of the column
8
- * @param params.columnLabel - The label of the column, e.g. "Status"
8
+ * @param params.column - The TanStack Table column object
9
9
  * @param params.allColumnValues - The values to filter by
10
10
  * @param params.renderValueLabel - A function that renders the label for a value
11
- * @param params.columnValuesFilter - The current state of the column filter
12
- * @param params.setColumnValuesFilter - A function that sets the state of the column filter
13
11
  */
14
- export declare function CategoricalColumnFilter<T extends readonly any[]>({ columnId, columnLabel, allColumnValues, renderValueLabel, columnValuesFilter, setColumnValuesFilter, }: {
15
- columnId: string;
16
- columnLabel: string;
17
- allColumnValues: T;
12
+ export declare function CategoricalColumnFilter<TData, TValue>({ column, allColumnValues, renderValueLabel, }: {
13
+ column: Column<TData, TValue>;
14
+ allColumnValues: TValue[];
18
15
  renderValueLabel?: (props: {
19
- value: T[number];
16
+ value: TValue;
20
17
  isSelected: boolean;
21
18
  }) => JSX.Element;
22
- columnValuesFilter: T[number][];
23
- setColumnValuesFilter: (value: T[number][]) => void;
24
19
  }): JSX.Element;
25
20
  //# sourceMappingURL=CategoricalColumnFilter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"CategoricalColumnFilter.d.ts","sourceRoot":"","sources":["../../src/components/CategoricalColumnFilter.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,GAAG,EAAqB,MAAM,eAAe,CAAC;AAiB5D;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,SAAS,GAAG,EAAE,EAAE,EAChE,QAAQ,EACR,WAAW,EACX,eAAe,EACf,gBAA0C,EAC1C,kBAAkB,EAClB,qBAAqB,GACtB,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,CAAC,CAAC;IACnB,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,KAAK,GAAG,CAAC,OAAO,CAAC;IACrF,kBAAkB,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;IAChC,qBAAqB,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,IAAI,CAAC;CACrD,eA8FA"}
1
+ {"version":3,"file":"CategoricalColumnFilter.d.ts","sourceRoot":"","sources":["../../src/components/CategoricalColumnFilter.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAEpD,OAAO,EAAE,KAAK,GAAG,EAAqB,MAAM,eAAe,CAAC;AAiB5D;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,EACrD,MAAM,EACN,eAAe,EACf,gBAA0C,GAC3C,EAAE;IACD,MAAM,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC9B,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,KAAK,GAAG,CAAC,OAAO,CAAC;CACnF,eAoIA"}
@@ -9,27 +9,31 @@ function computeSelected(allStatusValues, mode, selected) {
9
9
  return new Set(allStatusValues.filter((s) => !selected.has(s)));
10
10
  }
11
11
  function defaultRenderValueLabel({ value }) {
12
- return _jsx("span", { children: String(value) });
12
+ return _jsx("span", { class: "text-nowrap", children: String(value) });
13
13
  }
14
14
  /**
15
- * A component that allows the user to filter a categorical column. State is managed by the parent component.
15
+ * A component that allows the user to filter a categorical column.
16
16
  * The filter mode always defaults to "include".
17
17
  *
18
18
  * @param params
19
- * @param params.columnId - The ID of the column
20
- * @param params.columnLabel - The label of the column, e.g. "Status"
19
+ * @param params.column - The TanStack Table column object
21
20
  * @param params.allColumnValues - The values to filter by
22
21
  * @param params.renderValueLabel - A function that renders the label for a value
23
- * @param params.columnValuesFilter - The current state of the column filter
24
- * @param params.setColumnValuesFilter - A function that sets the state of the column filter
25
22
  */
26
- export function CategoricalColumnFilter({ columnId, columnLabel, allColumnValues, renderValueLabel = defaultRenderValueLabel, columnValuesFilter, setColumnValuesFilter, }) {
23
+ export function CategoricalColumnFilter({ column, allColumnValues, renderValueLabel = defaultRenderValueLabel, }) {
27
24
  const [mode, setMode] = useState('include');
28
- const selected = useMemo(() => computeSelected(allColumnValues, mode, new Set(columnValuesFilter)), [mode, columnValuesFilter, allColumnValues]);
25
+ const columnId = column.id;
26
+ const label = column.columnDef.meta?.label ??
27
+ (typeof column.columnDef.header === 'string' ? column.columnDef.header : column.id);
28
+ const columnValuesFilter = column.getFilterValue();
29
+ const selected = useMemo(() => {
30
+ return computeSelected(allColumnValues, mode, new Set(columnValuesFilter));
31
+ }, [mode, allColumnValues, columnValuesFilter]);
29
32
  const apply = (newMode, newSelected) => {
30
33
  const selected = computeSelected(allColumnValues, newMode, newSelected);
31
34
  setMode(newMode);
32
- setColumnValuesFilter(Array.from(selected));
35
+ const newValue = Array.from(selected);
36
+ column.setFilterValue(newValue);
33
37
  };
34
38
  const toggleSelected = (value) => {
35
39
  const set = new Set(selected);
@@ -41,12 +45,20 @@ export function CategoricalColumnFilter({ columnId, columnLabel, allColumnValues
41
45
  }
42
46
  apply(mode, set);
43
47
  };
44
- return (_jsxs(Dropdown, { align: "end", children: [_jsx(Dropdown.Toggle, { variant: "link", class: "text-muted p-0", id: `filter-${columnId}`, "aria-label": `Filter ${columnLabel.toLowerCase()}`, title: `Filter ${columnLabel.toLowerCase()}`, children: _jsx("i", { class: clsx('bi', selected.size > 0 ? ['bi-funnel-fill', 'text-primary'] : 'bi-funnel'), "aria-hidden": "true" }) }), _jsx(Dropdown.Menu, { class: "p-0", children: _jsxs("div", { class: "p-3", children: [_jsxs("div", { class: "d-flex align-items-center justify-content-between mb-2", children: [_jsx("div", { class: "fw-semibold", children: columnLabel }), _jsx("button", { type: "button", class: "btn btn-link btn-sm text-decoration-none", onClick: () => apply(mode, new Set()), children: "Clear" })] }), _jsxs("div", { class: "btn-group w-100 mb-2", role: "group", "aria-label": "Include or exclude values", children: [_jsx("button", { type: "button", class: clsx('btn', mode === 'include' ? 'btn-primary' : 'btn-outline-secondary'), onClick: () => apply('include', selected), children: "Include" }), _jsx("button", { type: "button", class: clsx('btn', mode === 'exclude' ? 'btn-primary' : 'btn-outline-secondary'), onClick: () => apply('exclude', selected), children: "Exclude" })] }), _jsx("div", { class: "list-group list-group-flush", children: allColumnValues.map((value) => {
45
- const isSelected = selected.has(value);
46
- return (_jsxs("div", { class: "list-group-item d-flex align-items-center gap-3", children: [_jsx("input", { class: "form-check-input", type: "checkbox", checked: isSelected, id: `${columnId}-${value}`, onChange: () => toggleSelected(value) }), _jsx("label", { class: "form-check-label", for: `${columnId}-${value}`, children: renderValueLabel({
48
+ return (_jsxs(Dropdown, { align: "end", children: [_jsx(Dropdown.Toggle, { variant: "link", class: "text-muted p-0", id: `filter-${columnId}`, "aria-label": `Filter ${label.toLowerCase()}`, title: `Filter ${label.toLowerCase()}`, children: _jsx("i", { class: clsx('bi', selected.size > 0 ? ['bi-funnel-fill', 'text-primary'] : 'bi-funnel'), "aria-hidden": "true" }) }), _jsxs(Dropdown.Menu, { class: "p-0", children: [_jsxs("div", { class: "p-3 pb-0", children: [_jsxs("div", { class: "d-flex align-items-center justify-content-between mb-2", children: [_jsx("div", { class: "fw-semibold text-nowrap", children: label }), _jsx("button", { type: "button", class: clsx('btn btn-link btn-sm text-decoration-none', {
49
+ // Hide the clear button if no filters are applied.
50
+ // Use `visibility` instead of conditional rendering to avoid layout shift.
51
+ invisible: selected.size === 0 && mode === 'include',
52
+ }), onClick: () => apply('include', new Set()), children: "Clear" })] }), _jsxs("div", { class: "btn-group btn-group-sm w-100 mb-2", children: [_jsx("input", { type: "radio", class: "btn-check", name: `filter-${columnId}-options`, id: `filter-${columnId}-include`, autocomplete: "off", checked: mode === 'include', onChange: () => apply('include', selected) }), _jsx("label", { class: "btn btn-outline-primary", for: `filter-${columnId}-include`, children: _jsxs("span", { class: "text-nowrap", children: [mode === 'include' && _jsx("i", { class: "bi bi-check-lg me-1", "aria-hidden": "true" }), "Include"] }) }), _jsx("input", { type: "radio", class: "btn-check", name: `filter-${columnId}-options`, id: `filter-${columnId}-exclude`, autocomplete: "off", checked: mode === 'exclude', onChange: () => apply('exclude', selected) }), _jsx("label", { class: "btn btn-outline-primary", for: `filter-${columnId}-exclude`, children: _jsxs("span", { class: "text-nowrap", children: [mode === 'exclude' && _jsx("i", { class: "bi bi-check-lg me-1", "aria-hidden": "true" }), "Exclude"] }) })] })] }), _jsx("div", { class: "list-group list-group-flush", style: {
53
+ // This is needed to prevent the last item's background from covering
54
+ // the dropdown's border radius.
55
+ '--bs-list-group-bg': 'transparent',
56
+ }, children: allColumnValues.map((value) => {
57
+ const isSelected = selected.has(value);
58
+ return (_jsx("div", { class: "list-group-item d-flex align-items-center gap-3", children: _jsxs("div", { class: "form-check", children: [_jsx("input", { class: "form-check-input", type: "checkbox", checked: isSelected, id: `${columnId}-${value}`, onChange: () => toggleSelected(value) }), _jsx("label", { class: "form-check-label fw-normal", for: `${columnId}-${value}`, children: renderValueLabel({
47
59
  value,
48
60
  isSelected,
49
- }) })] }, value));
50
- }) })] }) })] }));
61
+ }) })] }) }, value));
62
+ }) })] })] }));
51
63
  }
52
64
  //# sourceMappingURL=CategoricalColumnFilter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CategoricalColumnFilter.js","sourceRoot":"","sources":["../../src/components/CategoricalColumnFilter.tsx"],"names":[],"mappings":";AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAY,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,QAAQ,MAAM,0BAA0B,CAAC;AAEhD,SAAS,eAAe,CACtB,eAAkB,EAClB,IAA2B,EAC3B,QAAwB;IAExB,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,OAAO,IAAI,GAAG,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClE,CAAC;AAED,SAAS,uBAAuB,CAAI,EAAE,KAAK,EAAgB;IACzD,OAAO,yBAAO,MAAM,CAAC,KAAK,CAAC,GAAQ,CAAC;AACtC,CAAC;AACD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,uBAAuB,CAA2B,EAChE,QAAQ,EACR,WAAW,EACX,eAAe,EACf,gBAAgB,GAAG,uBAAuB,EAC1C,kBAAkB,EAClB,qBAAqB,GAQtB;IACC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAwB,SAAS,CAAC,CAAC;IAEnE,MAAM,QAAQ,GAAG,OAAO,CACtB,GAAG,EAAE,CAAC,eAAe,CAAC,eAAe,EAAE,IAAI,EAAE,IAAI,GAAG,CAAC,kBAAkB,CAAC,CAAC,EACzE,CAAC,IAAI,EAAE,kBAAkB,EAAE,eAAe,CAAC,CAC5C,CAAC;IAEF,MAAM,KAAK,GAAG,CAAC,OAA8B,EAAE,WAA2B,EAAE,EAAE;QAC5E,MAAM,QAAQ,GAAG,eAAe,CAAC,eAAe,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;QACxE,OAAO,CAAC,OAAO,CAAC,CAAC;QACjB,qBAAqB,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC9C,CAAC,CAAC;IAEF,MAAM,cAAc,GAAG,CAAC,KAAgB,EAAE,EAAE;QAC1C,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC9B,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YACnB,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACpB,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC;QACD,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACnB,CAAC,CAAC;IAEF,OAAO,CACL,MAAC,QAAQ,IAAC,KAAK,EAAC,KAAK,aACnB,KAAC,QAAQ,CAAC,MAAM,IACd,OAAO,EAAC,MAAM,EACd,KAAK,EAAC,gBAAgB,EACtB,EAAE,EAAE,UAAU,QAAQ,EAAE,gBACZ,UAAU,WAAW,CAAC,WAAW,EAAE,EAAE,EACjD,KAAK,EAAE,UAAU,WAAW,CAAC,WAAW,EAAE,EAAE,YAE5C,YACE,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,gBAAgB,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,iBAC3E,MAAM,GAClB,GACc,EAClB,KAAC,QAAQ,CAAC,IAAI,IAAC,KAAK,EAAC,KAAK,YACxB,eAAK,KAAK,EAAC,KAAK,aACd,eAAK,KAAK,EAAC,wDAAwD,aACjE,cAAK,KAAK,EAAC,aAAa,YAAE,WAAW,GAAO,EAC5C,iBACE,IAAI,EAAC,QAAQ,EACb,KAAK,EAAC,0CAA0C,EAChD,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,EAAE,CAAC,sBAG9B,IACL,EAEN,eAAK,KAAK,EAAC,sBAAsB,EAAC,IAAI,EAAC,OAAO,gBAAY,2BAA2B,aACnF,iBACE,IAAI,EAAC,QAAQ,EACb,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,uBAAuB,CAAC,EAChF,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,QAAQ,CAAC,wBAGlC,EACT,iBACE,IAAI,EAAC,QAAQ,EACb,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,uBAAuB,CAAC,EAChF,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,QAAQ,CAAC,wBAGlC,IACL,EAEN,cAAK,KAAK,EAAC,6BAA6B,YACrC,eAAe,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;gCAC7B,MAAM,UAAU,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gCACvC,OAAO,CACL,eAAiB,KAAK,EAAC,iDAAiD,aACtE,gBACE,KAAK,EAAC,kBAAkB,EACxB,IAAI,EAAC,UAAU,EACf,OAAO,EAAE,UAAU,EACnB,EAAE,EAAE,GAAG,QAAQ,IAAI,KAAK,EAAE,EAC1B,QAAQ,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,KAAK,CAAC,GACrC,EACF,gBAAO,KAAK,EAAC,kBAAkB,EAAC,GAAG,EAAE,GAAG,QAAQ,IAAI,KAAK,EAAE,YACxD,gBAAgB,CAAC;gDAChB,KAAK;gDACL,UAAU;6CACX,CAAC,GACI,KAbA,KAAK,CAcT,CACP,CAAC;4BACJ,CAAC,CAAC,GACE,IACF,GACQ,IACP,CACZ,CAAC;AACJ,CAAC","sourcesContent":["import clsx from 'clsx';\nimport { type JSX, useMemo, useState } from 'preact/compat';\nimport Dropdown from 'react-bootstrap/Dropdown';\n\nfunction computeSelected<T extends readonly any[]>(\n allStatusValues: T,\n mode: 'include' | 'exclude',\n selected: Set<T[number]>,\n) {\n if (mode === 'include') {\n return selected;\n }\n return new Set(allStatusValues.filter((s) => !selected.has(s)));\n}\n\nfunction defaultRenderValueLabel<T>({ value }: { value: T }) {\n return <span>{String(value)}</span>;\n}\n/**\n * A component that allows the user to filter a categorical column. State is managed by the parent component.\n * The filter mode always defaults to \"include\".\n *\n * @param params\n * @param params.columnId - The ID of the column\n * @param params.columnLabel - The label of the column, e.g. \"Status\"\n * @param params.allColumnValues - The values to filter by\n * @param params.renderValueLabel - A function that renders the label for a value\n * @param params.columnValuesFilter - The current state of the column filter\n * @param params.setColumnValuesFilter - A function that sets the state of the column filter\n */\nexport function CategoricalColumnFilter<T extends readonly any[]>({\n columnId,\n columnLabel,\n allColumnValues,\n renderValueLabel = defaultRenderValueLabel,\n columnValuesFilter,\n setColumnValuesFilter,\n}: {\n columnId: string;\n columnLabel: string;\n allColumnValues: T;\n renderValueLabel?: (props: { value: T[number]; isSelected: boolean }) => JSX.Element;\n columnValuesFilter: T[number][];\n setColumnValuesFilter: (value: T[number][]) => void;\n}) {\n const [mode, setMode] = useState<'include' | 'exclude'>('include');\n\n const selected = useMemo(\n () => computeSelected(allColumnValues, mode, new Set(columnValuesFilter)),\n [mode, columnValuesFilter, allColumnValues],\n );\n\n const apply = (newMode: 'include' | 'exclude', newSelected: Set<T[number]>) => {\n const selected = computeSelected(allColumnValues, newMode, newSelected);\n setMode(newMode);\n setColumnValuesFilter(Array.from(selected));\n };\n\n const toggleSelected = (value: T[number]) => {\n const set = new Set(selected);\n if (set.has(value)) {\n set.delete(value);\n } else {\n set.add(value);\n }\n apply(mode, set);\n };\n\n return (\n <Dropdown align=\"end\">\n <Dropdown.Toggle\n variant=\"link\"\n class=\"text-muted p-0\"\n id={`filter-${columnId}`}\n aria-label={`Filter ${columnLabel.toLowerCase()}`}\n title={`Filter ${columnLabel.toLowerCase()}`}\n >\n <i\n class={clsx('bi', selected.size > 0 ? ['bi-funnel-fill', 'text-primary'] : 'bi-funnel')}\n aria-hidden=\"true\"\n />\n </Dropdown.Toggle>\n <Dropdown.Menu class=\"p-0\">\n <div class=\"p-3\">\n <div class=\"d-flex align-items-center justify-content-between mb-2\">\n <div class=\"fw-semibold\">{columnLabel}</div>\n <button\n type=\"button\"\n class=\"btn btn-link btn-sm text-decoration-none\"\n onClick={() => apply(mode, new Set())}\n >\n Clear\n </button>\n </div>\n\n <div class=\"btn-group w-100 mb-2\" role=\"group\" aria-label=\"Include or exclude values\">\n <button\n type=\"button\"\n class={clsx('btn', mode === 'include' ? 'btn-primary' : 'btn-outline-secondary')}\n onClick={() => apply('include', selected)}\n >\n Include\n </button>\n <button\n type=\"button\"\n class={clsx('btn', mode === 'exclude' ? 'btn-primary' : 'btn-outline-secondary')}\n onClick={() => apply('exclude', selected)}\n >\n Exclude\n </button>\n </div>\n\n <div class=\"list-group list-group-flush\">\n {allColumnValues.map((value) => {\n const isSelected = selected.has(value);\n return (\n <div key={value} class=\"list-group-item d-flex align-items-center gap-3\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n checked={isSelected}\n id={`${columnId}-${value}`}\n onChange={() => toggleSelected(value)}\n />\n <label class=\"form-check-label\" for={`${columnId}-${value}`}>\n {renderValueLabel({\n value,\n isSelected,\n })}\n </label>\n </div>\n );\n })}\n </div>\n </div>\n </Dropdown.Menu>\n </Dropdown>\n );\n}\n"]}
1
+ {"version":3,"file":"CategoricalColumnFilter.js","sourceRoot":"","sources":["../../src/components/CategoricalColumnFilter.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { Column } from '@tanstack/react-table';\nimport clsx from 'clsx';\nimport { type JSX, useMemo, useState } from 'preact/compat';\nimport Dropdown from 'react-bootstrap/Dropdown';\n\nfunction computeSelected<T extends readonly any[]>(\n allStatusValues: T,\n mode: 'include' | 'exclude',\n selected: Set<T[number]>,\n) {\n if (mode === 'include') {\n return selected;\n }\n return new Set(allStatusValues.filter((s) => !selected.has(s)));\n}\n\nfunction defaultRenderValueLabel<T>({ value }: { value: T }) {\n return <span class=\"text-nowrap\">{String(value)}</span>;\n}\n/**\n * A component that allows the user to filter a categorical column.\n * The filter mode always defaults to \"include\".\n *\n * @param params\n * @param params.column - The TanStack Table column object\n * @param params.allColumnValues - The values to filter by\n * @param params.renderValueLabel - A function that renders the label for a value\n */\nexport function CategoricalColumnFilter<TData, TValue>({\n column,\n allColumnValues,\n renderValueLabel = defaultRenderValueLabel,\n}: {\n column: Column<TData, TValue>;\n allColumnValues: TValue[];\n renderValueLabel?: (props: { value: TValue; isSelected: boolean }) => JSX.Element;\n}) {\n const [mode, setMode] = useState<'include' | 'exclude'>('include');\n\n const columnId = column.id;\n\n const label =\n column.columnDef.meta?.label ??\n (typeof column.columnDef.header === 'string' ? column.columnDef.header : column.id);\n\n const columnValuesFilter = column.getFilterValue() as TValue[] | undefined;\n\n const selected = useMemo(() => {\n return computeSelected(allColumnValues, mode, new Set(columnValuesFilter));\n }, [mode, allColumnValues, columnValuesFilter]);\n\n const apply = (newMode: 'include' | 'exclude', newSelected: Set<TValue>) => {\n const selected = computeSelected(allColumnValues, newMode, newSelected);\n setMode(newMode);\n const newValue = Array.from(selected);\n column.setFilterValue(newValue);\n };\n\n const toggleSelected = (value: TValue) => {\n const set = new Set(selected);\n if (set.has(value)) {\n set.delete(value);\n } else {\n set.add(value);\n }\n apply(mode, set);\n };\n\n return (\n <Dropdown align=\"end\">\n <Dropdown.Toggle\n variant=\"link\"\n class=\"text-muted p-0\"\n id={`filter-${columnId}`}\n aria-label={`Filter ${label.toLowerCase()}`}\n title={`Filter ${label.toLowerCase()}`}\n >\n <i\n class={clsx('bi', selected.size > 0 ? ['bi-funnel-fill', 'text-primary'] : 'bi-funnel')}\n aria-hidden=\"true\"\n />\n </Dropdown.Toggle>\n <Dropdown.Menu class=\"p-0\">\n <div class=\"p-3 pb-0\">\n <div class=\"d-flex align-items-center justify-content-between mb-2\">\n <div class=\"fw-semibold text-nowrap\">{label}</div>\n <button\n type=\"button\"\n class={clsx('btn btn-link btn-sm text-decoration-none', {\n // Hide the clear button if no filters are applied.\n // Use `visibility` instead of conditional rendering to avoid layout shift.\n invisible: selected.size === 0 && mode === 'include',\n })}\n onClick={() => apply('include', new Set())}\n >\n Clear\n </button>\n </div>\n\n <div class=\"btn-group btn-group-sm w-100 mb-2\">\n <input\n type=\"radio\"\n class=\"btn-check\"\n name={`filter-${columnId}-options`}\n id={`filter-${columnId}-include`}\n autocomplete=\"off\"\n checked={mode === 'include'}\n onChange={() => apply('include', selected)}\n />\n <label class=\"btn btn-outline-primary\" for={`filter-${columnId}-include`}>\n <span class=\"text-nowrap\">\n {mode === 'include' && <i class=\"bi bi-check-lg me-1\" aria-hidden=\"true\" />}\n Include\n </span>\n </label>\n\n <input\n type=\"radio\"\n class=\"btn-check\"\n name={`filter-${columnId}-options`}\n id={`filter-${columnId}-exclude`}\n autocomplete=\"off\"\n checked={mode === 'exclude'}\n onChange={() => apply('exclude', selected)}\n />\n <label class=\"btn btn-outline-primary\" for={`filter-${columnId}-exclude`}>\n <span class=\"text-nowrap\">\n {mode === 'exclude' && <i class=\"bi bi-check-lg me-1\" aria-hidden=\"true\" />}\n Exclude\n </span>\n </label>\n </div>\n </div>\n\n <div\n class=\"list-group list-group-flush\"\n style={{\n // This is needed to prevent the last item's background from covering\n // the dropdown's border radius.\n '--bs-list-group-bg': 'transparent',\n }}\n >\n {allColumnValues.map((value) => {\n const isSelected = selected.has(value);\n return (\n <div key={value} class=\"list-group-item d-flex align-items-center gap-3\">\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n checked={isSelected}\n id={`${columnId}-${value}`}\n onChange={() => toggleSelected(value)}\n />\n <label class=\"form-check-label fw-normal\" for={`${columnId}-${value}`}>\n {renderValueLabel({\n value,\n isSelected,\n })}\n </label>\n </div>\n </div>\n );\n })}\n </div>\n </Dropdown.Menu>\n </Dropdown>\n );\n}\n"]}
@@ -1,5 +1,9 @@
1
1
  import { type Table } from '@tanstack/react-table';
2
- export declare function ColumnManager<RowDataModel>({ table }: {
2
+ import { type JSX } from 'preact/compat';
3
+ interface ColumnManagerProps<RowDataModel> {
3
4
  table: Table<RowDataModel>;
4
- }): import("preact/compat").JSX.Element;
5
+ topContent?: JSX.Element;
6
+ }
7
+ export declare function ColumnManager<RowDataModel>({ table, topContent, }: ColumnManagerProps<RowDataModel>): JSX.Element;
8
+ export {};
5
9
  //# sourceMappingURL=ColumnManager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ColumnManager.d.ts","sourceRoot":"","sources":["../../src/components/ColumnManager.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAgGhE,wBAAgB,aAAa,CAAC,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,KAAK,CAAC,YAAY,CAAC,CAAA;CAAE,uCA2HpF"}
1
+ {"version":3,"file":"ColumnManager.d.ts","sourceRoot":"","sources":["../../src/components/ColumnManager.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAEhE,OAAO,EAAE,KAAK,GAAG,EAA+B,MAAM,eAAe,CAAC;AA4KtE,UAAU,kBAAkB,CAAC,YAAY;IACvC,KAAK,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IAC3B,UAAU,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC;CAC1B;AAED,wBAAgB,aAAa,CAAC,YAAY,EAAE,EAC1C,KAAK,EACL,UAAU,GACX,EAAE,kBAAkB,CAAC,YAAY,CAAC,eAyLlC"}
@@ -1,53 +1,70 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "@prairielearn/preact-cjs/jsx-runtime";
2
2
  import {} from '@tanstack/react-table';
3
+ import clsx from 'clsx';
3
4
  import { useEffect, useRef, useState } from 'preact/compat';
4
5
  import Button from 'react-bootstrap/Button';
5
6
  import Dropdown from 'react-bootstrap/Dropdown';
6
- import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
7
- import Tooltip from 'react-bootstrap/Tooltip';
8
- function ColumnMenuItem({ column, hidePinButton = false, onTogglePin, onClearElementFocus, }) {
9
- const pinButtonRef = useRef(null);
10
- if (!column.getCanHide() && !column.getCanPin())
7
+ function ColumnLeafItem({ column, onPinningBoundary = false, onTogglePin, className, }) {
8
+ if (!column.getCanHide())
11
9
  return null;
12
10
  // Use meta.label if available, otherwise fall back to header or column.id
13
11
  const header = column.columnDef.meta?.label ??
14
12
  (typeof column.columnDef.header === 'string' ? column.columnDef.header : column.id);
15
- return (_jsxs(Dropdown.Item, { as: "div", class: "px-2 py-1 d-flex align-items-center justify-content-between", onKeyDown: onClearElementFocus, children: [_jsxs("label", { class: "form-check me-auto text-nowrap d-flex align-items-stretch", children: [_jsx(OverlayTrigger, { placement: "top", overlay: _jsx(Tooltip, { children: column.getIsVisible() ? 'Hide column' : 'Show column' }), children: _jsx("input", { type: "checkbox", class: "form-check-input", checked: column.getIsVisible(), disabled: !column.getCanHide(), "aria-label": column.getIsVisible() ? `Hide '${header}' column` : `Show '${header}' column`, "aria-describedby": `${column.id}-label`, onChange: column.getToggleVisibilityHandler() }) }), _jsx("span", { class: "form-check-label ms-2", id: `${column.id}-label`, children: header })] }), column.getCanPin() && !hidePinButton && (_jsx("button", { ref: pinButtonRef, type: "button",
13
+ return (_jsxs("div", { class: clsx('px-2 py-1 d-flex align-items-center justify-content-between', className), children: [_jsxs("label", { class: "form-check me-auto text-nowrap d-flex align-items-stretch", children: [_jsx("input", { type: "checkbox", class: "form-check-input", checked: column.getIsVisible(), disabled: !column.getCanHide(), "aria-label": column.getIsVisible() ? `Hide '${header}' column` : `Show '${header}' column`, "aria-describedby": `${column.id}-label`, onChange: column.getToggleVisibilityHandler() }), _jsx("span", { class: "form-check-label ms-2", id: `${column.id}-label`, children: header })] }), _jsx("button", { type: "button",
16
14
  // Since the HTML changes, but we want to refocus the pin button, we track
17
15
  // the active pin button and refocuses it when the column manager is rerendered.
18
- id: `${column.id}-pin`, class: "btn btn-sm btn-ghost ms-2", "aria-label": column.getIsPinned() ? `Unfreeze '${header}' column` : `Freeze '${header}' column`, title: column.getIsPinned() ? 'Unfreeze column' : 'Freeze column', "data-bs-toggle": "tooltip", tabIndex: 0, onKeyDown: (e) => {
19
- if (!pinButtonRef.current) {
20
- throw new Error('pinButtonRef.current is null');
21
- }
22
- if (e.key === 'Enter' || e.key === ' ') {
23
- e.preventDefault();
24
- onTogglePin(column.id);
25
- return;
26
- }
27
- },
28
- // Instead, use the arrow keys to move between interactive elements in each menu item.
29
- onClick: () => {
30
- if (!pinButtonRef.current) {
31
- throw new Error('pinButtonRef.current is null');
32
- }
33
- onTogglePin(column.id);
34
- }, children: _jsx("i", { class: `bi ${column.getIsPinned() ? 'bi-x' : 'bi-snow'}`, "aria-hidden": "true" }) }))] }, column.id));
16
+ id: `${column.id}-pin`, class: clsx('btn btn-sm btn-ghost ms-2', (!column.getCanPin() || !onPinningBoundary) && 'invisible'), "aria-label": column.getIsPinned() ? `Unfreeze '${header}' column` : `Freeze '${header}' column`, title: column.getIsPinned() ? 'Unfreeze column' : 'Freeze column', "data-bs-toggle": "tooltip", onClick: () => onTogglePin(column.id), children: _jsx("i", { class: `bi ${column.getIsPinned() ? 'bi-x' : 'bi-snow'}`, "aria-hidden": "true" }) })] }, column.id));
35
17
  }
36
- export function ColumnManager({ table }) {
18
+ function ColumnGroupItem({ column, onTogglePin, getIsOnPinningBoundary, }) {
19
+ const [isExpanded, setIsExpanded] = useState(false);
20
+ const leafColumns = column.getLeafColumns();
21
+ const visibleLeafColumns = leafColumns.filter((c) => c.getIsVisible());
22
+ const isAllVisible = visibleLeafColumns.length === leafColumns.length;
23
+ const isSomeVisible = visibleLeafColumns.length > 0 && !isAllVisible;
24
+ const handleToggleVisibility = (e) => {
25
+ e.preventDefault();
26
+ e.stopPropagation();
27
+ const targetVisibility = !isAllVisible;
28
+ leafColumns.forEach((col) => {
29
+ if (col.getCanHide()) {
30
+ col.toggleVisibility(targetVisibility);
31
+ }
32
+ });
33
+ };
34
+ // Use meta.label if available, otherwise fall back to header or column.id
35
+ const header = column.columnDef.meta?.label ??
36
+ (typeof column.columnDef.header === 'string' ? column.columnDef.header : column.id);
37
+ return (_jsxs("div", { class: "d-flex flex-column", children: [_jsx("div", { class: "px-2 py-1 d-flex align-items-center justify-content-between", children: _jsxs("div", { class: "d-flex align-items-center flex-grow-1", children: [_jsx("input", { type: "checkbox", class: "form-check-input flex-shrink-0", checked: isAllVisible, indeterminate: isSomeVisible, "aria-label": `Toggle visibility for group '${header}'`, onChange: handleToggleVisibility }), _jsxs("button", { type: "button", class: "btn btn-link text-decoration-none text-reset w-100 text-start d-flex align-items-center justify-content-between ps-2 py-0 pe-0", "aria-expanded": isExpanded, onClick: (e) => {
38
+ e.stopPropagation();
39
+ setIsExpanded(!isExpanded);
40
+ }, children: [_jsx("span", { class: "fw-bold text-truncate", children: header }), _jsx("i", { class: clsx('bi ms-2 text-muted', isExpanded ? 'bi-chevron-down' : 'bi-chevron-right'), "aria-hidden": "true" })] })] }) }), isExpanded && (_jsx("div", { class: "ps-3 border-start ms-3 mb-1", children: column.columns.map((childCol) => (_jsx(ColumnItem, { column: childCol, getIsOnPinningBoundary: getIsOnPinningBoundary, onTogglePin: onTogglePin }, childCol.id))) }))] }));
41
+ }
42
+ function ColumnItem({ column, onTogglePin, getIsOnPinningBoundary, }) {
43
+ if (column.columns.length > 0) {
44
+ return (_jsx(ColumnGroupItem, { column: column, getIsOnPinningBoundary: getIsOnPinningBoundary, onTogglePin: onTogglePin }));
45
+ }
46
+ return (_jsx(ColumnLeafItem, { column: column, onPinningBoundary: getIsOnPinningBoundary(column.id), onTogglePin: onTogglePin }));
47
+ }
48
+ export function ColumnManager({ table, topContent, }) {
37
49
  const [activeElementId, setActiveElementId] = useState(null);
38
50
  const [dropdownOpen, setDropdownOpen] = useState(false);
39
51
  const menuRef = useRef(null);
40
52
  const handleTogglePin = (columnId) => {
41
53
  const currentLeft = table.getState().columnPinning.left ?? [];
42
54
  const isPinned = currentLeft.includes(columnId);
55
+ const allLeafColumns = table.getAllLeafColumns();
56
+ const currentColumnIndex = allLeafColumns.findIndex((c) => c.id === columnId);
43
57
  let newLeft;
44
58
  if (isPinned) {
45
- newLeft = currentLeft.filter((id) => id !== columnId);
59
+ // Get the previous column that can be set to unpinned.
60
+ // This is useful since we want to unpin/pin columns that are not shown in the view manager.
61
+ const previousFrozenColumnIndex = allLeafColumns.findLastIndex((c, index) => c.getCanHide() && index < currentColumnIndex);
62
+ newLeft = allLeafColumns.slice(0, previousFrozenColumnIndex + 1).map((c) => c.id);
46
63
  }
47
64
  else {
48
- const columnOrder = table.getAllLeafColumns().map((c) => c.id);
49
- const newPinned = new Set([...currentLeft, columnId]);
50
- newLeft = columnOrder.filter((id) => newPinned.has(id));
65
+ // Pin all columns to the left of the current column.
66
+ const leftColumns = allLeafColumns.slice(0, currentColumnIndex + 1);
67
+ newLeft = leftColumns.map((c) => c.id);
51
68
  }
52
69
  table.setColumnPinning({ left: newLeft, right: [] });
53
70
  setActiveElementId(`${columnId}-pin`);
@@ -60,8 +77,47 @@ export function ColumnManager({ table }) {
60
77
  const isPinningChanged = initialPinning.length !== currentPinning.length ||
61
78
  initialPinning.some((id) => !currentPinning.includes(id));
62
79
  const showResetButton = isVisibilityChanged || isPinningChanged;
63
- const pinnedColumns = table.getAllLeafColumns().filter((c) => c.getIsPinned() === 'left');
64
- const unpinnedColumns = table.getAllLeafColumns().filter((c) => c.getIsPinned() !== 'left');
80
+ const allLeafColumns = table.getAllLeafColumns();
81
+ const pinnedMenuColumns = allLeafColumns.filter((c) => c.getCanHide() && c.getIsPinned() === 'left');
82
+ // Only the first unpinned menu column can be pinned, so we only need to find the first one
83
+ const firstUnpinnedMenuColumn = allLeafColumns.find((c) => c.getCanHide() && c.getIsPinned() !== 'left');
84
+ // Determine if a column is on the pinning boundary (can toggle its pin state).
85
+ // - Columns in a group cannot be pinned
86
+ // - Columns after a group cannot be pinned
87
+ // - Only the last pinned menu column can be unpinned
88
+ // - Only the first unpinned menu column can be pinned
89
+ const getIsOnPinningBoundary = (columnId) => {
90
+ const column = allLeafColumns.find((c) => c.id === columnId);
91
+ if (!column)
92
+ return false;
93
+ // Columns in a group cannot be pinned
94
+ if (column.parent)
95
+ return false;
96
+ // Check if any column at or before this one in the full column order is in a group
97
+ const columnIdx = allLeafColumns.findIndex((c) => c.id === columnId);
98
+ const hasGroupAtOrBefore = allLeafColumns.slice(0, columnIdx + 1).some((c) => c.parent);
99
+ if (column.getIsPinned() === 'left') {
100
+ // Only the last pinned menu column can be unpinned
101
+ return columnId === pinnedMenuColumns[pinnedMenuColumns.length - 1]?.id;
102
+ }
103
+ else {
104
+ // Cannot pin if there's a group at or before this column
105
+ if (hasGroupAtOrBefore)
106
+ return false;
107
+ // Only the first unpinned menu column can be pinned
108
+ return columnId === firstUnpinnedMenuColumn?.id;
109
+ }
110
+ };
111
+ // Get root columns (for showing hierarchy), but filter to only show unpinned ones
112
+ // We'll show pinned columns separately in the "Frozen columns" section
113
+ const unpinnedRootColumns = table.getAllColumns().filter((c) => {
114
+ if (c.depth !== 0)
115
+ return false;
116
+ // A root column is considered unpinned if all its leaf columns are unpinned
117
+ const leafCols = c.getLeafColumns();
118
+ return (leafCols.length > 0 &&
119
+ leafCols.every((leaf) => leaf.getIsPinned() !== 'left' && c.getCanHide()));
120
+ });
65
121
  useEffect(() => {
66
122
  // When we use the pin or reset button, we want to refocus to another element.
67
123
  // We want this in a useEffect so that this code runs after the component re-renders.
@@ -75,14 +131,21 @@ export function ColumnManager({ table }) {
75
131
  if (menuRef.current && !menuRef.current.contains(e.target)) {
76
132
  setDropdownOpen(false);
77
133
  }
78
- }, children: [_jsxs(Dropdown.Toggle, { variant: "outline-secondary", id: "column-manager-button", children: [_jsx("i", { class: "bi bi-view-list me-2", "aria-hidden": "true" }), "View"] }), _jsxs(Dropdown.Menu, { style: { maxHeight: '60vh', overflowY: 'auto' }, children: [pinnedColumns.length > 0 && (_jsxs(_Fragment, { children: [_jsx("div", { class: "px-2 py-1 text-muted small", role: "presentation", children: "Frozen columns" }), _jsx("div", { role: "group", children: pinnedColumns.map((column, index) => {
79
- return (_jsx(ColumnMenuItem, { column: column, hidePinButton: index !== pinnedColumns.length - 1, onTogglePin: handleTogglePin, onClearElementFocus: () => setActiveElementId(null) }, column.id));
80
- }) }), _jsx(Dropdown.Divider, {})] })), unpinnedColumns.length > 0 && (_jsxs(_Fragment, { children: [_jsx("div", { role: "group", children: unpinnedColumns.map((column, index) => {
81
- return (_jsx(ColumnMenuItem, { column: column, hidePinButton: index !== 0, onTogglePin: handleTogglePin, onClearElementFocus: () => setActiveElementId(null) }, column.id));
134
+ }, children: [_jsxs(Dropdown.Toggle
135
+ // We assume that this component will only appear once per page. If that changes,
136
+ // we'll need to do something to ensure ID uniqueness here.
137
+ , {
138
+ // We assume that this component will only appear once per page. If that changes,
139
+ // we'll need to do something to ensure ID uniqueness here.
140
+ id: "column-manager", variant: "tanstack-table", children: [_jsx("i", { class: "bi bi-view-list me-2", "aria-hidden": "true" }), " View", ' '] }), _jsxs(Dropdown.Menu, { style: { maxHeight: '60vh', overflowY: 'auto' }, children: [topContent && (_jsxs(_Fragment, { children: [topContent, _jsx(Dropdown.Divider, {})] })), pinnedMenuColumns.length > 0 && (_jsxs(_Fragment, { children: [_jsx("div", { class: "px-2 py-1 text-muted small", role: "presentation", children: "Frozen columns" }), _jsx("div", { role: "group", children: pinnedMenuColumns.map((column, index) => {
141
+ return (_jsx(ColumnLeafItem, { column: column, onPinningBoundary: index === pinnedMenuColumns.length - 1, onTogglePin: handleTogglePin }, column.id));
142
+ }) }), _jsx(Dropdown.Divider, {})] })), unpinnedRootColumns.length > 0 && (_jsxs(_Fragment, { children: [_jsx("div", { role: "group", children: unpinnedRootColumns.map((column) => {
143
+ return (_jsx(ColumnItem, { column: column, getIsOnPinningBoundary: getIsOnPinningBoundary, onTogglePin: handleTogglePin }, column.id));
82
144
  }) }), showResetButton && _jsx(Dropdown.Divider, {})] })), showResetButton && (_jsx("div", { class: "px-2 py-1", children: _jsxs(Button, { variant: "secondary", size: "sm", class: "w-100", "aria-label": "Reset all columns to default visibility and pinning", onClick: () => {
83
145
  table.resetColumnVisibility();
84
146
  table.resetColumnPinning();
85
- setActiveElementId('column-manager-button');
147
+ // Move focus to the column manager button after resetting.
148
+ setActiveElementId('column-manager');
86
149
  }, children: [_jsx("i", { class: "bi bi-arrow-counterclockwise me-2", "aria-hidden": "true" }), "Reset view"] }) }))] })] }));
87
150
  }
88
151
  //# sourceMappingURL=ColumnManager.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ColumnManager.js","sourceRoot":"","sources":["../../src/components/ColumnManager.tsx"],"names":[],"mappings":"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","sourcesContent":["import { type Column, type Table } from '@tanstack/react-table';\nimport { useEffect, useRef, useState } from 'preact/compat';\nimport Button from 'react-bootstrap/Button';\nimport Dropdown from 'react-bootstrap/Dropdown';\nimport OverlayTrigger from 'react-bootstrap/OverlayTrigger';\nimport Tooltip from 'react-bootstrap/Tooltip';\n\ninterface ColumnMenuItemProps<RowDataModel> {\n column: Column<RowDataModel>;\n hidePinButton: boolean;\n onTogglePin: (columnId: string) => void;\n onClearElementFocus: () => void;\n}\n\nfunction ColumnMenuItem<RowDataModel>({\n column,\n hidePinButton = false,\n onTogglePin,\n onClearElementFocus,\n}: ColumnMenuItemProps<RowDataModel>) {\n const pinButtonRef = useRef<HTMLButtonElement>(null);\n\n if (!column.getCanHide() && !column.getCanPin()) return null;\n\n // Use meta.label if available, otherwise fall back to header or column.id\n const header =\n (column.columnDef.meta as any)?.label ??\n (typeof column.columnDef.header === 'string' ? column.columnDef.header : column.id);\n\n return (\n <Dropdown.Item\n key={column.id}\n as=\"div\"\n class=\"px-2 py-1 d-flex align-items-center justify-content-between\"\n onKeyDown={onClearElementFocus}\n >\n <label class=\"form-check me-auto text-nowrap d-flex align-items-stretch\">\n <OverlayTrigger\n placement=\"top\"\n overlay={<Tooltip>{column.getIsVisible() ? 'Hide column' : 'Show column'}</Tooltip>}\n >\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n checked={column.getIsVisible()}\n disabled={!column.getCanHide()}\n aria-label={\n column.getIsVisible() ? `Hide '${header}' column` : `Show '${header}' column`\n }\n aria-describedby={`${column.id}-label`}\n onChange={column.getToggleVisibilityHandler()}\n />\n </OverlayTrigger>\n <span class=\"form-check-label ms-2\" id={`${column.id}-label`}>\n {header}\n </span>\n </label>\n {column.getCanPin() && !hidePinButton && (\n <button\n ref={pinButtonRef}\n type=\"button\"\n // Since the HTML changes, but we want to refocus the pin button, we track\n // the active pin button and refocuses it when the column manager is rerendered.\n id={`${column.id}-pin`}\n class=\"btn btn-sm btn-ghost ms-2\"\n aria-label={\n column.getIsPinned() ? `Unfreeze '${header}' column` : `Freeze '${header}' column`\n }\n title={column.getIsPinned() ? 'Unfreeze column' : 'Freeze column'}\n data-bs-toggle=\"tooltip\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (!pinButtonRef.current) {\n throw new Error('pinButtonRef.current is null');\n }\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onTogglePin(column.id);\n return;\n }\n }}\n // Instead, use the arrow keys to move between interactive elements in each menu item.\n onClick={() => {\n if (!pinButtonRef.current) {\n throw new Error('pinButtonRef.current is null');\n }\n onTogglePin(column.id);\n }}\n >\n <i class={`bi ${column.getIsPinned() ? 'bi-x' : 'bi-snow'}`} aria-hidden=\"true\" />\n </button>\n )}\n </Dropdown.Item>\n );\n}\n\nexport function ColumnManager<RowDataModel>({ table }: { table: Table<RowDataModel> }) {\n const [activeElementId, setActiveElementId] = useState<string | null>(null);\n const [dropdownOpen, setDropdownOpen] = useState(false);\n const menuRef = useRef<HTMLDivElement>(null);\n const handleTogglePin = (columnId: string) => {\n const currentLeft = table.getState().columnPinning.left ?? [];\n const isPinned = currentLeft.includes(columnId);\n let newLeft: string[];\n if (isPinned) {\n newLeft = currentLeft.filter((id) => id !== columnId);\n } else {\n const columnOrder = table.getAllLeafColumns().map((c) => c.id);\n const newPinned = new Set([...currentLeft, columnId]);\n newLeft = columnOrder.filter((id) => newPinned.has(id));\n }\n table.setColumnPinning({ left: newLeft, right: [] });\n setActiveElementId(`${columnId}-pin`);\n };\n\n const isVisibilityChanged = Object.entries(table.getState().columnVisibility).some(\n ([key, value]) => {\n return value !== table.initialState.columnVisibility[key];\n },\n );\n\n const initialPinning = table.initialState.columnPinning.left ?? [];\n const currentPinning = table.getState().columnPinning.left ?? [];\n const isPinningChanged =\n initialPinning.length !== currentPinning.length ||\n initialPinning.some((id) => !currentPinning.includes(id));\n const showResetButton = isVisibilityChanged || isPinningChanged;\n\n const pinnedColumns = table.getAllLeafColumns().filter((c) => c.getIsPinned() === 'left');\n const unpinnedColumns = table.getAllLeafColumns().filter((c) => c.getIsPinned() !== 'left');\n\n useEffect(() => {\n // When we use the pin or reset button, we want to refocus to another element.\n // We want this in a useEffect so that this code runs after the component re-renders.\n\n // eslint-disable-next-line react-you-might-not-need-an-effect/no-event-handler\n if (activeElementId) {\n document.getElementById(activeElementId)?.focus();\n }\n }, [activeElementId]);\n\n return (\n <Dropdown\n ref={menuRef}\n autoClose=\"outside\"\n show={dropdownOpen}\n onToggle={(isOpen, _meta) => setDropdownOpen(isOpen)}\n onFocusOut={(e: FocusEvent) => {\n // Since we aren't using role=\"menu\", we need to manually close the dropdown when focus leaves.\n if (menuRef.current && !menuRef.current.contains(e.target as Node)) {\n setDropdownOpen(false);\n }\n }}\n >\n <Dropdown.Toggle variant=\"outline-secondary\" id=\"column-manager-button\">\n <i class=\"bi bi-view-list me-2\" aria-hidden=\"true\" />\n View\n </Dropdown.Toggle>\n <Dropdown.Menu style={{ maxHeight: '60vh', overflowY: 'auto' }}>\n {pinnedColumns.length > 0 && (\n <>\n <div class=\"px-2 py-1 text-muted small\" role=\"presentation\">\n Frozen columns\n </div>\n <div role=\"group\">\n {pinnedColumns.map((column, index) => {\n return (\n <ColumnMenuItem\n key={column.id}\n column={column}\n hidePinButton={index !== pinnedColumns.length - 1}\n onTogglePin={handleTogglePin}\n onClearElementFocus={() => setActiveElementId(null)}\n />\n );\n })}\n </div>\n <Dropdown.Divider />\n </>\n )}\n {unpinnedColumns.length > 0 && (\n <>\n <div role=\"group\">\n {unpinnedColumns.map((column, index) => {\n return (\n <ColumnMenuItem\n key={column.id}\n column={column}\n hidePinButton={index !== 0}\n onTogglePin={handleTogglePin}\n onClearElementFocus={() => setActiveElementId(null)}\n />\n );\n })}\n </div>\n {showResetButton && <Dropdown.Divider />}\n </>\n )}\n {showResetButton && (\n <div class=\"px-2 py-1\">\n <Button\n variant=\"secondary\"\n size=\"sm\"\n class=\"w-100\"\n aria-label=\"Reset all columns to default visibility and pinning\"\n onClick={() => {\n table.resetColumnVisibility();\n table.resetColumnPinning();\n setActiveElementId('column-manager-button');\n }}\n >\n <i class=\"bi bi-arrow-counterclockwise me-2\" aria-hidden=\"true\" />\n Reset view\n </Button>\n </div>\n )}\n </Dropdown.Menu>\n </Dropdown>\n );\n}\n"]}
1
+ {"version":3,"file":"ColumnManager.js","sourceRoot":"","sources":["../../src/components/ColumnManager.tsx"],"names":[],"mappings":"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","sourcesContent":["import { type Column, type Table } from '@tanstack/react-table';\nimport clsx from 'clsx';\nimport { type JSX, useEffect, useRef, useState } from 'preact/compat';\nimport Button from 'react-bootstrap/Button';\nimport Dropdown from 'react-bootstrap/Dropdown';\n\ninterface ColumnMenuItemProps<RowDataModel> {\n column: Column<RowDataModel>;\n onPinningBoundary: boolean;\n onTogglePin: (columnId: string) => void;\n className?: string;\n}\n\nfunction ColumnLeafItem<RowDataModel>({\n column,\n onPinningBoundary = false,\n onTogglePin,\n className,\n}: ColumnMenuItemProps<RowDataModel>) {\n if (!column.getCanHide()) return null;\n\n // Use meta.label if available, otherwise fall back to header or column.id\n const header =\n column.columnDef.meta?.label ??\n (typeof column.columnDef.header === 'string' ? column.columnDef.header : column.id);\n\n return (\n <div\n key={column.id}\n class={clsx('px-2 py-1 d-flex align-items-center justify-content-between', className)}\n >\n <label class=\"form-check me-auto text-nowrap d-flex align-items-stretch\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n checked={column.getIsVisible()}\n disabled={!column.getCanHide()}\n aria-label={column.getIsVisible() ? `Hide '${header}' column` : `Show '${header}' column`}\n aria-describedby={`${column.id}-label`}\n onChange={column.getToggleVisibilityHandler()}\n />\n <span class=\"form-check-label ms-2\" id={`${column.id}-label`}>\n {header}\n </span>\n </label>\n <button\n type=\"button\"\n // Since the HTML changes, but we want to refocus the pin button, we track\n // the active pin button and refocuses it when the column manager is rerendered.\n id={`${column.id}-pin`}\n class={clsx(\n 'btn btn-sm btn-ghost ms-2',\n (!column.getCanPin() || !onPinningBoundary) && 'invisible',\n )}\n aria-label={\n column.getIsPinned() ? `Unfreeze '${header}' column` : `Freeze '${header}' column`\n }\n title={column.getIsPinned() ? 'Unfreeze column' : 'Freeze column'}\n data-bs-toggle=\"tooltip\"\n onClick={() => onTogglePin(column.id)}\n >\n <i class={`bi ${column.getIsPinned() ? 'bi-x' : 'bi-snow'}`} aria-hidden=\"true\" />\n </button>\n </div>\n );\n}\n\nfunction ColumnGroupItem<RowDataModel>({\n column,\n onTogglePin,\n getIsOnPinningBoundary,\n}: {\n column: Column<RowDataModel>;\n onTogglePin: (columnId: string) => void;\n getIsOnPinningBoundary: (columnId: string) => boolean;\n}) {\n const [isExpanded, setIsExpanded] = useState(false);\n\n const leafColumns = column.getLeafColumns();\n const visibleLeafColumns = leafColumns.filter((c) => c.getIsVisible());\n const isAllVisible = visibleLeafColumns.length === leafColumns.length;\n const isSomeVisible = visibleLeafColumns.length > 0 && !isAllVisible;\n\n const handleToggleVisibility = (e: Event) => {\n e.preventDefault();\n e.stopPropagation();\n const targetVisibility = !isAllVisible;\n leafColumns.forEach((col) => {\n if (col.getCanHide()) {\n col.toggleVisibility(targetVisibility);\n }\n });\n };\n\n // Use meta.label if available, otherwise fall back to header or column.id\n const header =\n column.columnDef.meta?.label ??\n (typeof column.columnDef.header === 'string' ? column.columnDef.header : column.id);\n\n return (\n <div class=\"d-flex flex-column\">\n <div class=\"px-2 py-1 d-flex align-items-center justify-content-between\">\n <div class=\"d-flex align-items-center flex-grow-1\">\n <input\n type=\"checkbox\"\n class=\"form-check-input flex-shrink-0\"\n checked={isAllVisible}\n indeterminate={isSomeVisible}\n aria-label={`Toggle visibility for group '${header}'`}\n onChange={handleToggleVisibility}\n />\n <button\n type=\"button\"\n class=\"btn btn-link text-decoration-none text-reset w-100 text-start d-flex align-items-center justify-content-between ps-2 py-0 pe-0\"\n aria-expanded={isExpanded}\n onClick={(e) => {\n e.stopPropagation();\n setIsExpanded(!isExpanded);\n }}\n >\n <span class=\"fw-bold text-truncate\">{header}</span>\n <i\n class={clsx(\n 'bi ms-2 text-muted',\n isExpanded ? 'bi-chevron-down' : 'bi-chevron-right',\n )}\n aria-hidden=\"true\"\n />\n </button>\n </div>\n </div>\n {isExpanded && (\n <div class=\"ps-3 border-start ms-3 mb-1\">\n {column.columns.map((childCol) => (\n <ColumnItem\n key={childCol.id}\n column={childCol}\n getIsOnPinningBoundary={getIsOnPinningBoundary}\n onTogglePin={onTogglePin}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n\nfunction ColumnItem<RowDataModel>({\n column,\n onTogglePin,\n getIsOnPinningBoundary,\n}: {\n column: Column<RowDataModel>;\n onTogglePin: (columnId: string) => void;\n getIsOnPinningBoundary: (columnId: string) => boolean;\n}) {\n if (column.columns.length > 0) {\n return (\n <ColumnGroupItem\n column={column}\n getIsOnPinningBoundary={getIsOnPinningBoundary}\n onTogglePin={onTogglePin}\n />\n );\n }\n return (\n <ColumnLeafItem\n column={column}\n onPinningBoundary={getIsOnPinningBoundary(column.id)}\n onTogglePin={onTogglePin}\n />\n );\n}\n\ninterface ColumnManagerProps<RowDataModel> {\n table: Table<RowDataModel>;\n topContent?: JSX.Element;\n}\n\nexport function ColumnManager<RowDataModel>({\n table,\n topContent,\n}: ColumnManagerProps<RowDataModel>) {\n const [activeElementId, setActiveElementId] = useState<string | null>(null);\n const [dropdownOpen, setDropdownOpen] = useState(false);\n const menuRef = useRef<HTMLDivElement>(null);\n const handleTogglePin = (columnId: string) => {\n const currentLeft = table.getState().columnPinning.left ?? [];\n const isPinned = currentLeft.includes(columnId);\n const allLeafColumns = table.getAllLeafColumns();\n const currentColumnIndex = allLeafColumns.findIndex((c) => c.id === columnId);\n let newLeft: string[];\n if (isPinned) {\n // Get the previous column that can be set to unpinned.\n // This is useful since we want to unpin/pin columns that are not shown in the view manager.\n const previousFrozenColumnIndex = allLeafColumns.findLastIndex(\n (c, index) => c.getCanHide() && index < currentColumnIndex,\n );\n newLeft = allLeafColumns.slice(0, previousFrozenColumnIndex + 1).map((c) => c.id);\n } else {\n // Pin all columns to the left of the current column.\n const leftColumns = allLeafColumns.slice(0, currentColumnIndex + 1);\n newLeft = leftColumns.map((c) => c.id);\n }\n table.setColumnPinning({ left: newLeft, right: [] });\n setActiveElementId(`${columnId}-pin`);\n };\n\n const isVisibilityChanged = Object.entries(table.getState().columnVisibility).some(\n ([key, value]) => {\n return value !== table.initialState.columnVisibility[key];\n },\n );\n\n const initialPinning = table.initialState.columnPinning.left ?? [];\n const currentPinning = table.getState().columnPinning.left ?? [];\n const isPinningChanged =\n initialPinning.length !== currentPinning.length ||\n initialPinning.some((id) => !currentPinning.includes(id));\n const showResetButton = isVisibilityChanged || isPinningChanged;\n\n const allLeafColumns = table.getAllLeafColumns();\n const pinnedMenuColumns = allLeafColumns.filter(\n (c) => c.getCanHide() && c.getIsPinned() === 'left',\n );\n // Only the first unpinned menu column can be pinned, so we only need to find the first one\n const firstUnpinnedMenuColumn = allLeafColumns.find(\n (c) => c.getCanHide() && c.getIsPinned() !== 'left',\n );\n\n // Determine if a column is on the pinning boundary (can toggle its pin state).\n // - Columns in a group cannot be pinned\n // - Columns after a group cannot be pinned\n // - Only the last pinned menu column can be unpinned\n // - Only the first unpinned menu column can be pinned\n const getIsOnPinningBoundary = (columnId: string) => {\n const column = allLeafColumns.find((c) => c.id === columnId);\n if (!column) return false;\n\n // Columns in a group cannot be pinned\n if (column.parent) return false;\n\n // Check if any column at or before this one in the full column order is in a group\n const columnIdx = allLeafColumns.findIndex((c) => c.id === columnId);\n const hasGroupAtOrBefore = allLeafColumns.slice(0, columnIdx + 1).some((c) => c.parent);\n\n if (column.getIsPinned() === 'left') {\n // Only the last pinned menu column can be unpinned\n return columnId === pinnedMenuColumns[pinnedMenuColumns.length - 1]?.id;\n } else {\n // Cannot pin if there's a group at or before this column\n if (hasGroupAtOrBefore) return false;\n // Only the first unpinned menu column can be pinned\n return columnId === firstUnpinnedMenuColumn?.id;\n }\n };\n\n // Get root columns (for showing hierarchy), but filter to only show unpinned ones\n // We'll show pinned columns separately in the \"Frozen columns\" section\n const unpinnedRootColumns = table.getAllColumns().filter((c) => {\n if (c.depth !== 0) return false;\n // A root column is considered unpinned if all its leaf columns are unpinned\n const leafCols = c.getLeafColumns();\n return (\n leafCols.length > 0 &&\n leafCols.every((leaf) => leaf.getIsPinned() !== 'left' && c.getCanHide())\n );\n });\n\n useEffect(() => {\n // When we use the pin or reset button, we want to refocus to another element.\n // We want this in a useEffect so that this code runs after the component re-renders.\n\n // eslint-disable-next-line react-you-might-not-need-an-effect/no-event-handler\n if (activeElementId) {\n document.getElementById(activeElementId)?.focus();\n }\n }, [activeElementId]);\n\n return (\n <Dropdown\n ref={menuRef}\n autoClose=\"outside\"\n show={dropdownOpen}\n onToggle={(isOpen, _meta) => setDropdownOpen(isOpen)}\n onFocusOut={(e: FocusEvent) => {\n // Since we aren't using role=\"menu\", we need to manually close the dropdown when focus leaves.\n if (menuRef.current && !menuRef.current.contains(e.target as Node)) {\n setDropdownOpen(false);\n }\n }}\n >\n <Dropdown.Toggle\n // We assume that this component will only appear once per page. If that changes,\n // we'll need to do something to ensure ID uniqueness here.\n id=\"column-manager\"\n variant=\"tanstack-table\"\n >\n <i class=\"bi bi-view-list me-2\" aria-hidden=\"true\" /> View{' '}\n </Dropdown.Toggle>\n <Dropdown.Menu style={{ maxHeight: '60vh', overflowY: 'auto' }}>\n {topContent && (\n <>\n {topContent}\n <Dropdown.Divider />\n </>\n )}\n {pinnedMenuColumns.length > 0 && (\n <>\n <div class=\"px-2 py-1 text-muted small\" role=\"presentation\">\n Frozen columns\n </div>\n <div role=\"group\">\n {/* Only leaf columns can be pinned in the current implementation. */}\n {pinnedMenuColumns.map((column, index) => {\n return (\n <ColumnLeafItem\n key={column.id}\n column={column}\n onPinningBoundary={index === pinnedMenuColumns.length - 1}\n onTogglePin={handleTogglePin}\n />\n );\n })}\n </div>\n <Dropdown.Divider />\n </>\n )}\n {unpinnedRootColumns.length > 0 && (\n <>\n <div role=\"group\">\n {unpinnedRootColumns.map((column) => {\n return (\n <ColumnItem\n key={column.id}\n column={column}\n getIsOnPinningBoundary={getIsOnPinningBoundary}\n onTogglePin={handleTogglePin}\n />\n );\n })}\n </div>\n {showResetButton && <Dropdown.Divider />}\n </>\n )}\n {showResetButton && (\n <div class=\"px-2 py-1\">\n <Button\n variant=\"secondary\"\n size=\"sm\"\n class=\"w-100\"\n aria-label=\"Reset all columns to default visibility and pinning\"\n onClick={() => {\n table.resetColumnVisibility();\n table.resetColumnPinning();\n // Move focus to the column manager button after resetting.\n setActiveElementId('column-manager');\n }}\n >\n <i class=\"bi bi-arrow-counterclockwise me-2\" aria-hidden=\"true\" />\n Reset view\n </Button>\n </div>\n )}\n </Dropdown.Menu>\n </Dropdown>\n );\n}\n"]}
@@ -1,25 +1,21 @@
1
+ import type { Column } from '@tanstack/table-core';
1
2
  import { type JSX } from 'preact/compat';
2
3
  /**
3
4
  * A component that allows the user to filter a column containing arrays of values.
4
5
  * Uses AND logic: rows must contain ALL selected values to match.
5
6
  *
6
7
  * @param params
7
- * @param params.columnId - The ID of the column
8
- * @param params.columnLabel - The label of the column, e.g. "Rubric Items"
9
- * @param params.allColumnValues - All possible values that can appear in the column
8
+ * @param params.column - The TanStack Table column object
9
+ * @param params.allColumnValues - All possible values that can appear in the column filter
10
10
  * @param params.renderValueLabel - A function that renders the label for a value
11
- * @param params.columnValuesFilter - The current state of the column filter
12
- * @param params.setColumnValuesFilter - A function that sets the state of the column filter
13
11
  */
14
- export declare function MultiSelectColumnFilter<T extends readonly any[]>({ columnId, columnLabel, allColumnValues, renderValueLabel, columnValuesFilter, setColumnValuesFilter, }: {
15
- columnId: string;
16
- columnLabel: string;
17
- allColumnValues: T;
12
+ export declare function MultiSelectColumnFilter<TData, TValue>({ column, allColumnValues, renderValueLabel, }: {
13
+ column: Column<TData, TValue>;
14
+ /** In some cases, the filter values are not the same as the column values, but `TValue` is a good estimation. */
15
+ allColumnValues: TValue[];
18
16
  renderValueLabel?: (props: {
19
- value: T[number];
17
+ value: TValue;
20
18
  isSelected: boolean;
21
19
  }) => JSX.Element;
22
- columnValuesFilter: T[number][];
23
- setColumnValuesFilter: (value: T[number][]) => void;
24
20
  }): JSX.Element;
25
21
  //# sourceMappingURL=MultiSelectColumnFilter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MultiSelectColumnFilter.d.ts","sourceRoot":"","sources":["../../src/components/MultiSelectColumnFilter.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,GAAG,EAAW,MAAM,eAAe,CAAC;AAOlD;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,SAAS,GAAG,EAAE,EAAE,EAChE,QAAQ,EACR,WAAW,EACX,eAAe,EACf,gBAA0C,EAC1C,kBAAkB,EAClB,qBAAqB,GACtB,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,CAAC,CAAC;IACnB,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,KAAK,GAAG,CAAC,OAAO,CAAC;IACrF,kBAAkB,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;IAChC,qBAAqB,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,IAAI,CAAC;CACrD,eAoEA"}
1
+ {"version":3,"file":"MultiSelectColumnFilter.d.ts","sourceRoot":"","sources":["../../src/components/MultiSelectColumnFilter.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAEnD,OAAO,EAAE,KAAK,GAAG,EAAW,MAAM,eAAe,CAAC;AAOlD;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,EACrD,MAAM,EACN,eAAe,EACf,gBAA0C,GAC3C,EAAE;IACD,MAAM,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC9B,iHAAiH;IACjH,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,KAAK,GAAG,CAAC,OAAO,CAAC;CACnF,eAwFA"}