このコミットが含まれているのは:
2026-10-28 22:28:12 +09:00
コミット ecd8b449e3
9個のファイルの変更、95行の追加、16行の削除
+6 -2
ファイルの表示
@@ -92,6 +92,7 @@ const CountsControls = (
const update = (key: string, value: string) => setDraft (previous => {
const next = new URLSearchParams (previous)
setCountState (next, key, value)
if (key === 'limit') next.delete ('page')
if (next.toString () === previous.toString ()) return previous
if (['rows', 'cols'].includes (key) && next.get ('sort') !== '') next.delete ('sort')
if (key !== 'mode') return next
@@ -153,10 +154,13 @@ const CountsControls = (
<legend className="sr-only">{label ('displaySettings')}</legend>
<label className="inline-flex items-center gap-1 text-sm">
{label ('fields.limit')}
<input value={draft.get ('limit') ?? '20'}
<select value={draft.get ('limit') ?? '100'}
aria-label={message ('fields.limit').text}
className={inputClass (false, 'w-20 py-1')}
onChange={event => update ('limit', event.target.value)}/>
onChange={event => update ('limit', event.target.value)}>
{[20, 50, 100].map (count => <option key={count} value={count}>
{label ('shared:labels.resultsCount', { count })}</option>)}
</select>
</label>
<label className="inline-flex items-center gap-1 text-sm">
<input type="checkbox" checked={draft.get ('row_numbers') === '1'}
+1 -1
ファイルの表示
@@ -35,7 +35,7 @@ const CountsResults = (
const columns = selectedCountColumns (resource, params, definition)
const rowNumbers = params.get ('row_numbers') === '1'
const fields = fieldsForCounts (resource, definition)
const sorts = readCountSort (params.get ('sort') ?? 'created_at:desc')
const sorts = readCountSort (params.get ('sort') ?? 'resource_id:asc version_no:asc')
const sort = (key: string, additive: boolean) =>
update ('sort', writeCountSort (changeCountSort (sorts, key, additive)))
const cell = (row: CountsVersion, column: CountColumn) => {
+43 -5
ファイルの表示
@@ -1,9 +1,10 @@
import { useEffect, useRef } from 'react'
import { useContext, useEffect, useRef } from 'react'
import SortableItem from '@/components/common/SortableItem'
import CountsResizeHandle from '@/components/counts/CountsResizeHandle'
import { Button } from '@/components/ui/button'
import { useUITranslation } from '@/lib/useUITranslation'
import { CountsTableScope, useCountsTableLayout } from '@/lib/countsTableLayout'
import type { CountSort } from '@/lib/counts'
@@ -19,6 +20,42 @@ const CountsSortHeader = (
dragId?: string; totalKind?: string },
) => {
const { message } = useUITranslation ('counts')
const family = useContext (CountsTableScope)
const layout = useCountsTableLayout ()
const heading = useRef<HTMLDivElement> (null)
useEffect (() => {
if (!resizable || colSpan != null && colSpan > 1) return
const node = heading.current
const text = node?.querySelector<HTMLElement> ('[data-counts-label]')
const button = text?.closest ('button')
if (node == null || text == null || button == null) return
const canvas = document.createElement ('canvas')
const context = canvas.getContext ('2d')
if (context == null) return
const measure = () => {
context.font = getComputedStyle (text).font
const padding = (element: Element) => {
const style = getComputedStyle (element)
return parseFloat (style.paddingLeft) + parseFloat (style.paddingRight)
+ parseFloat (style.borderLeftWidth) + parseFloat (style.borderRightWidth)
}
const handles = Array.from (node.children).filter (child => child !== button)
.reduce ((sum, child) => sum + Math.max (
child.scrollWidth, child.getBoundingClientRect ().width), 0)
const resize = node.parentElement?.querySelector ('[data-counts-resize]')
const resizeWidth = resize?.getBoundingClientRect ().width ?? 0
const indicator = button.querySelector ('[data-counts-sort-indicator]')
const sortWidth = indicator?.getBoundingClientRect ().width ?? 0
const gap = parseFloat (getComputedStyle (button).columnGap) || 0
layout.fit (family, field, context.measureText (label.text).width
+ handles + padding (button) + padding (node.parentElement!)
+ sortWidth + gap + resizeWidth)
}
measure ()
let active = true
void document.fonts.ready.then (() => { if (active) measure () })
return () => { active = false }
}, [family, field, label.text, dragId, resizable, colSpan, layout])
const press = useRef<{ x: number; y: number
timer: ReturnType<typeof setTimeout> } | null> (null)
const suppressClick = useRef (false)
@@ -44,7 +81,7 @@ const CountsSortHeader = (
data-counts-heading={label.text} rowSpan={rowSpan} colSpan={colSpan}
className="relative overflow-hidden whitespace-nowrap px-1 py-1"
aria-sort={(sorts.length > 1 && priority >= 0) ? 'other' : state}>
<div className="flex min-w-0 items-center">
<div ref={heading} className="flex min-w-0 items-center">
{shell?.handle}
<Button type="button" variant="ghost" size="sm"
className="h-auto w-full max-w-full justify-start select-none px-1 py-0 pr-3"
@@ -92,9 +129,10 @@ const CountsSortHeader = (
}
change (field, event.ctrlKey || event.metaKey)
}}>
<span className="truncate" lang={label.language}>{label.text}</span>
{priority >= 0 && <span aria-hidden>
{direction === 'asc' ? '▲' : '▼'}{sorts.length > 1 && priority + 1}</span>}
<span data-counts-label className="truncate" lang={label.language}>{label.text}</span>
<span aria-hidden data-counts-sort-indicator className="inline-block w-10 shrink-0">
{priority >= 0 && <>{direction === 'asc' ? '▲' : '▼'}
{sorts.length > 1 && priority + 1}</>}</span>
</Button>
</div>
{resizable && <CountsResizeHandle field={field} label={label}/>}
+11 -3
ファイルの表示
@@ -1,6 +1,7 @@
import { useState } from 'react'
import { CountsLayoutContext, defaultCountWidths, MAX_COUNT_WIDTH, MIN_COUNT_WIDTH }
import { CountsLayoutContext, defaultCountWidths, MAX_COUNT_WIDTH, MIN_COUNT_WIDTH,
MAX_INITIAL_COUNT_WIDTH }
from '@/lib/countsTableLayout'
import type { ReactNode } from 'react'
@@ -8,8 +9,15 @@ import type { ReactNode } from 'react'
const CountsTableLayout = ({ children }: { children: ReactNode }) => {
const [widths, setWidths] = useState<Record<string, number>> ({ })
const [headings, setHeadings] = useState<Record<string, number>> ({ })
const width = (family: string, key: string) =>
widths[`${family}/${key}`] ?? defaultCountWidths[key] ?? 160
widths[`${family}/${key}`]
?? Math.max (defaultCountWidths[key] ?? 160, headings[`${family}/${key}`] ?? 0)
const fit = (family: string, key: string, value: number) => {
const next = Math.ceil (Math.min (MAX_INITIAL_COUNT_WIDTH, value))
setHeadings (previous => previous[`${family}/${key}`] === next ? previous
: { ...previous, [`${family}/${key}`]: next })
}
const custom = (family: string, key: string) => widths[`${family}/${key}`] != null
const resize = (family: string, key: string, value: number) => {
const next = Math.round (Math.max (MIN_COUNT_WIDTH, Math.min (MAX_COUNT_WIDTH, value)))
@@ -17,7 +25,7 @@ const CountsTableLayout = ({ children }: { children: ReactNode }) => {
: { ...previous, [`${family}/${key}`]: next })
}
return (
<CountsLayoutContext.Provider value={{ width, custom, resize }}>
<CountsLayoutContext.Provider value={{ width, custom, resize, fit }}>
{children}
</CountsLayoutContext.Provider>)
}
+6 -1
ファイルの表示
@@ -74,7 +74,12 @@
}
.counts-grid > tbody > tr > td:has(> .counts-nested) {
padding: 0;
padding: 8px 12px;
}
.counts-nested .counts-grid > thead > tr > th,
.counts-nested .counts-grid > tbody > tr > td {
padding: 8px 12px;
}
.counts-nested {