@prairielearn/ui 1.7.2 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @prairielearn/ui
2
2
 
3
+ ## 1.8.0
4
+
5
+ ### Minor Changes
6
+
7
+ - e279b47: Add `useModalState` hook
8
+
9
+ ### Patch Changes
10
+
11
+ - 230c3a3: Replace usage of `findLastIndex` with ponyfill
12
+
3
13
  ## 1.7.2
4
14
 
5
15
  ### Patch Changes
@@ -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;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
+ {"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,eA0LlC"}
@@ -45,6 +45,18 @@ function ColumnItem({ column, onTogglePin, getIsOnPinningBoundary, }) {
45
45
  }
46
46
  return (_jsx(ColumnLeafItem, { column: column, onPinningBoundary: getIsOnPinningBoundary(column.id), onTogglePin: onTogglePin }));
47
47
  }
48
+ /**
49
+ * Ponyfill for `Array.prototype.findLastIndex`, which is not available in the
50
+ * `ES2022` TypeScript lib that we're currently using.
51
+ */
52
+ function findLastIndex(arr, predicate) {
53
+ for (let i = arr.length - 1; i >= 0; i--) {
54
+ if (predicate(arr[i], i)) {
55
+ return i;
56
+ }
57
+ }
58
+ return -1;
59
+ }
48
60
  export function ColumnManager({ table, topContent, }) {
49
61
  const [activeElementId, setActiveElementId] = useState(null);
50
62
  const [dropdownOpen, setDropdownOpen] = useState(false);
@@ -58,7 +70,7 @@ export function ColumnManager({ table, topContent, }) {
58
70
  if (isPinned) {
59
71
  // Get the previous column that can be set to unpinned.
60
72
  // 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);
73
+ const previousFrozenColumnIndex = findLastIndex(allLeafColumns, (c, index) => c.getCanHide() && index < currentColumnIndex);
62
74
  newLeft = allLeafColumns.slice(0, previousFrozenColumnIndex + 1).map((c) => c.id);
63
75
  }
64
76
  else {
@@ -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 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
+ {"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\n/**\n * Ponyfill for `Array.prototype.findLastIndex`, which is not available in the\n * `ES2022` TypeScript lib that we're currently using.\n */\nfunction findLastIndex<T>(arr: T[], predicate: (value: T, index: number) => boolean): number {\n for (let i = arr.length - 1; i >= 0; i--) {\n if (predicate(arr[i], i)) {\n return i;\n }\n }\n return -1;\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 = findLastIndex(\n allLeafColumns,\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"]}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * A hook to manage the state of a modal dialog that's rendered with a certain set of data.
3
+ *
4
+ * The main thing this hook achieves is to separate the "shown" state of the modal from the
5
+ * data that it's rendered with, so that when the modal is closed, the data is cleared only
6
+ * after the modal has finished closing (i.e., after any closing animation has completed).
7
+ * This avoids a flash of an empty modal if the data is cleared immediately when the modal is closed.
8
+ */
9
+ export declare function useModalState<T>(initialData?: T | null): {
10
+ show: boolean;
11
+ data: T | null;
12
+ showWithData: (data: T) => void;
13
+ hide: () => void;
14
+ onHide: () => void;
15
+ onExited: () => void;
16
+ };
17
+ //# sourceMappingURL=use-modal-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-modal-state.d.ts","sourceRoot":"","sources":["../../src/hooks/use-modal-state.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,WAAW,GAAE,CAAC,GAAG,IAAW;;;yBAMpC,CAAC;;;;EAQzB"}
@@ -0,0 +1,25 @@
1
+ import { useState } from 'preact/hooks';
2
+ /**
3
+ * A hook to manage the state of a modal dialog that's rendered with a certain set of data.
4
+ *
5
+ * The main thing this hook achieves is to separate the "shown" state of the modal from the
6
+ * data that it's rendered with, so that when the modal is closed, the data is cleared only
7
+ * after the modal has finished closing (i.e., after any closing animation has completed).
8
+ * This avoids a flash of an empty modal if the data is cleared immediately when the modal is closed.
9
+ */
10
+ export function useModalState(initialData = null) {
11
+ const [show, setShow] = useState(false);
12
+ const [data, setData] = useState(initialData);
13
+ return {
14
+ show,
15
+ data,
16
+ showWithData: (data) => {
17
+ setData(data);
18
+ setShow(true);
19
+ },
20
+ hide: () => setShow(false),
21
+ onHide: () => setShow(false),
22
+ onExited: () => setData(null),
23
+ };
24
+ }
25
+ //# sourceMappingURL=use-modal-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-modal-state.js","sourceRoot":"","sources":["../../src/hooks/use-modal-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExC;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAI,cAAwB,IAAI;IAC3D,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAU,KAAK,CAAC,CAAC;IACjD,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAW,WAAW,CAAC,CAAC;IACxD,OAAO;QACL,IAAI;QACJ,IAAI;QACJ,YAAY,EAAE,CAAC,IAAO,EAAE,EAAE;YACxB,OAAO,CAAC,IAAI,CAAC,CAAC;YACd,OAAO,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC;QACD,IAAI,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;QAC1B,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;QAC5B,QAAQ,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;KAC9B,CAAC;AACJ,CAAC","sourcesContent":["import { useState } from 'preact/hooks';\n\n/**\n * A hook to manage the state of a modal dialog that's rendered with a certain set of data.\n *\n * The main thing this hook achieves is to separate the \"shown\" state of the modal from the\n * data that it's rendered with, so that when the modal is closed, the data is cleared only\n * after the modal has finished closing (i.e., after any closing animation has completed).\n * This avoids a flash of an empty modal if the data is cleared immediately when the modal is closed.\n */\nexport function useModalState<T>(initialData: T | null = null) {\n const [show, setShow] = useState<boolean>(false);\n const [data, setData] = useState<T | null>(initialData);\n return {\n show,\n data,\n showWithData: (data: T) => {\n setData(data);\n setShow(true);\n },\n hide: () => setShow(false),\n onHide: () => setShow(false),\n onExited: () => setData(null),\n };\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -10,4 +10,5 @@ export { useAutoSizeColumns } from './components/useAutoSizeColumns.js';
10
10
  export { OverlayTrigger, type OverlayTriggerProps } from './components/OverlayTrigger.js';
11
11
  export { PresetFilterDropdown } from './components/PresetFilterDropdown.js';
12
12
  export { NuqsAdapter, parseAsSortingState, parseAsColumnVisibilityStateWithColumns, parseAsColumnPinningState, parseAsNumericFilter, } from './components/nuqs.js';
13
+ export { useModalState } from './hooks/use-modal-state.js';
13
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,kBAAkB,CAAC;AAE1B,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,uBAAuB,GACxB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAE,2BAA2B,EAAE,MAAM,6CAA6C,CAAC;AAC1F,OAAO,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAClF,OAAO,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAClF,OAAO,EACL,wBAAwB,EACxB,kBAAkB,EAClB,qBAAqB,EACrB,KAAK,wBAAwB,GAC9B,MAAM,0CAA0C,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAC1F,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAC5E,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,uCAAuC,EACvC,yBAAyB,EACzB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,kBAAkB,CAAC;AAE1B,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,uBAAuB,GACxB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAE,2BAA2B,EAAE,MAAM,6CAA6C,CAAC;AAC1F,OAAO,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAClF,OAAO,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAClF,OAAO,EACL,wBAAwB,EACxB,kBAAkB,EAClB,qBAAqB,EACrB,KAAK,wBAAwB,GAC9B,MAAM,0CAA0C,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAC1F,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAC5E,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,uCAAuC,EACvC,yBAAyB,EACzB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC"}
package/dist/index.js CHANGED
@@ -11,4 +11,5 @@ export { useAutoSizeColumns } from './components/useAutoSizeColumns.js';
11
11
  export { OverlayTrigger } from './components/OverlayTrigger.js';
12
12
  export { PresetFilterDropdown } from './components/PresetFilterDropdown.js';
13
13
  export { NuqsAdapter, parseAsSortingState, parseAsColumnVisibilityStateWithColumns, parseAsColumnPinningState, parseAsNumericFilter, } from './components/nuqs.js';
14
+ export { useModalState } from './hooks/use-modal-state.js';
14
15
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,kBAAkB,CAAC;AAE1B,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,uBAAuB,GACxB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAE,2BAA2B,EAAE,MAAM,6CAA6C,CAAC;AAC1F,OAAO,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAClF,OAAO,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAClF,OAAO,EACL,wBAAwB,EACxB,kBAAkB,EAClB,qBAAqB,GAEtB,MAAM,0CAA0C,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,EAAE,cAAc,EAA4B,MAAM,gCAAgC,CAAC;AAC1F,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAC5E,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,uCAAuC,EACvC,yBAAyB,EACzB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC","sourcesContent":["// Augment @tanstack/react-table types\nimport './react-table.js';\n\nexport {\n TanstackTable,\n TanstackTableCard,\n TanstackTableEmptyState,\n} from './components/TanstackTable.js';\nexport { ColumnManager } from './components/ColumnManager.js';\nexport { TanstackTableDownloadButton } from './components/TanstackTableDownloadButton.js';\nexport { CategoricalColumnFilter } from './components/CategoricalColumnFilter.js';\nexport { MultiSelectColumnFilter } from './components/MultiSelectColumnFilter.js';\nexport {\n NumericInputColumnFilter,\n parseNumericFilter,\n numericColumnFilterFn,\n type NumericColumnFilterValue,\n} from './components/NumericInputColumnFilter.js';\nexport { useShiftClickCheckbox } from './components/useShiftClickCheckbox.js';\nexport { useAutoSizeColumns } from './components/useAutoSizeColumns.js';\nexport { OverlayTrigger, type OverlayTriggerProps } from './components/OverlayTrigger.js';\nexport { PresetFilterDropdown } from './components/PresetFilterDropdown.js';\nexport {\n NuqsAdapter,\n parseAsSortingState,\n parseAsColumnVisibilityStateWithColumns,\n parseAsColumnPinningState,\n parseAsNumericFilter,\n} from './components/nuqs.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,kBAAkB,CAAC;AAE1B,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,uBAAuB,GACxB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAE,2BAA2B,EAAE,MAAM,6CAA6C,CAAC;AAC1F,OAAO,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAClF,OAAO,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAClF,OAAO,EACL,wBAAwB,EACxB,kBAAkB,EAClB,qBAAqB,GAEtB,MAAM,0CAA0C,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,EAAE,cAAc,EAA4B,MAAM,gCAAgC,CAAC;AAC1F,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAC5E,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,uCAAuC,EACvC,yBAAyB,EACzB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC","sourcesContent":["// Augment @tanstack/react-table types\nimport './react-table.js';\n\nexport {\n TanstackTable,\n TanstackTableCard,\n TanstackTableEmptyState,\n} from './components/TanstackTable.js';\nexport { ColumnManager } from './components/ColumnManager.js';\nexport { TanstackTableDownloadButton } from './components/TanstackTableDownloadButton.js';\nexport { CategoricalColumnFilter } from './components/CategoricalColumnFilter.js';\nexport { MultiSelectColumnFilter } from './components/MultiSelectColumnFilter.js';\nexport {\n NumericInputColumnFilter,\n parseNumericFilter,\n numericColumnFilterFn,\n type NumericColumnFilterValue,\n} from './components/NumericInputColumnFilter.js';\nexport { useShiftClickCheckbox } from './components/useShiftClickCheckbox.js';\nexport { useAutoSizeColumns } from './components/useAutoSizeColumns.js';\nexport { OverlayTrigger, type OverlayTriggerProps } from './components/OverlayTrigger.js';\nexport { PresetFilterDropdown } from './components/PresetFilterDropdown.js';\nexport {\n NuqsAdapter,\n parseAsSortingState,\n parseAsColumnVisibilityStateWithColumns,\n parseAsColumnPinningState,\n parseAsNumericFilter,\n} from './components/nuqs.js';\n\nexport { useModalState } from './hooks/use-modal-state.js';\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prairielearn/ui",
3
- "version": "1.7.2",
3
+ "version": "1.8.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -177,6 +177,19 @@ interface ColumnManagerProps<RowDataModel> {
177
177
  topContent?: JSX.Element;
178
178
  }
179
179
 
180
+ /**
181
+ * Ponyfill for `Array.prototype.findLastIndex`, which is not available in the
182
+ * `ES2022` TypeScript lib that we're currently using.
183
+ */
184
+ function findLastIndex<T>(arr: T[], predicate: (value: T, index: number) => boolean): number {
185
+ for (let i = arr.length - 1; i >= 0; i--) {
186
+ if (predicate(arr[i], i)) {
187
+ return i;
188
+ }
189
+ }
190
+ return -1;
191
+ }
192
+
180
193
  export function ColumnManager<RowDataModel>({
181
194
  table,
182
195
  topContent,
@@ -193,7 +206,8 @@ export function ColumnManager<RowDataModel>({
193
206
  if (isPinned) {
194
207
  // Get the previous column that can be set to unpinned.
195
208
  // This is useful since we want to unpin/pin columns that are not shown in the view manager.
196
- const previousFrozenColumnIndex = allLeafColumns.findLastIndex(
209
+ const previousFrozenColumnIndex = findLastIndex(
210
+ allLeafColumns,
197
211
  (c, index) => c.getCanHide() && index < currentColumnIndex,
198
212
  );
199
213
  newLeft = allLeafColumns.slice(0, previousFrozenColumnIndex + 1).map((c) => c.id);
@@ -0,0 +1,25 @@
1
+ import { useState } from 'preact/hooks';
2
+
3
+ /**
4
+ * A hook to manage the state of a modal dialog that's rendered with a certain set of data.
5
+ *
6
+ * The main thing this hook achieves is to separate the "shown" state of the modal from the
7
+ * data that it's rendered with, so that when the modal is closed, the data is cleared only
8
+ * after the modal has finished closing (i.e., after any closing animation has completed).
9
+ * This avoids a flash of an empty modal if the data is cleared immediately when the modal is closed.
10
+ */
11
+ export function useModalState<T>(initialData: T | null = null) {
12
+ const [show, setShow] = useState<boolean>(false);
13
+ const [data, setData] = useState<T | null>(initialData);
14
+ return {
15
+ show,
16
+ data,
17
+ showWithData: (data: T) => {
18
+ setData(data);
19
+ setShow(true);
20
+ },
21
+ hide: () => setShow(false),
22
+ onHide: () => setShow(false),
23
+ onExited: () => setData(null),
24
+ };
25
+ }
package/src/index.ts CHANGED
@@ -27,3 +27,5 @@ export {
27
27
  parseAsColumnPinningState,
28
28
  parseAsNumericFilter,
29
29
  } from './components/nuqs.js';
30
+
31
+ export { useModalState } from './hooks/use-modal-state.js';