このコミットが含まれているのは:
2026-10-28 20:57:22 +09:00
コミット 614a12e7aa
18個のファイルの変更、175行の追加、107行の削除
+30 -34
ファイルの表示
@@ -1,8 +1,8 @@
import { useId, useState } from 'react'
import { useId, useLayoutEffect, useRef, useState } from 'react'
import AnimatedCollapse from '@/components/common/AnimatedCollapse'
import SortableList from '@/components/common/SortableList'
import { fieldsForCounts, orderedCountFields,
import { countChildPaths, fieldsForCounts, normaliseCountsParams, orderedCountFields,
selectedCountChildren, selectedCountColumns, selectedCountFields } from '@/lib/counts'
import { useCountsDefinition } from '@/lib/countsDefinitions'
import { useClientMotion } from '@/lib/useClientMotion'
@@ -22,12 +22,20 @@ const FieldBranch = ({ field, path, enabled, selected, params, select, update }:
const [open, setOpen] = useState (false)
const id = useId ()
const motion = useClientMotion ()
const checked = selected.has (path)
const input = useRef<HTMLInputElement> (null)
const leaves = countChildPaths (field.children ?? [], path, true)
const count = leaves.filter (key => selected.has (key)).length
const checked = field.children == null ? selected.has (path)
: count > 0 && count === leaves.length
const partial = count > 0 && count < leaves.length
useLayoutEffect (() => {
if (input.current != null) input.current.indeterminate = partial
}, [partial])
return <>
<div className="flex items-center gap-1">
<label className="inline-flex items-center gap-1">
<input type="checkbox" checked={checked} disabled={!enabled || (field.children != null
&& !field.children.some (child => selected.has (`${path}.${child.key}`)))}
<input ref={input} type="checkbox" checked={checked} disabled={!enabled}
aria-checked={partial ? 'mixed' : checked}
onChange={event => select (path, event.target.checked)}/>
{label (field.label, field.labelValues)}
</label>
@@ -89,48 +97,36 @@ const CountsFieldTree = (
update: (key: string, value: string) => void },
) => {
const definition = useCountsDefinition ()
const resolved = normaliseCountsParams (resource, params, definition)
const fields = suppliedFields ?? fieldsForCounts (resource, definition)
const roots = suppliedFields == null ? selectedCountColumns (resource, params, definition)
: selectedCountFields (fields, params, selectionKey, parent).map (field => field.key)
const selected = selectedCountChildren (params)
const roots = suppliedFields == null ? selectedCountColumns (resource, resolved, definition)
: selectedCountFields (fields, resolved, selectionKey, parent).map (field => field.key)
const selected = selectedCountChildren (resolved, fieldsForCounts (resource, definition))
const rootPath = (key: string) => parent ? `${parent}.${key}` : key
for (const root of roots) selected.add (rootPath (root))
const select = (path: string, checked: boolean) => {
const root = fields.find (field => rootPath (field.key) === path)
if (root != null)
const next = new Set (selected)
const paths = countChildPaths (fields, parent)
.filter (key => key === path || key.startsWith (`${path}.`))
for (const key of paths)
{
const next = new Set<string> (roots)
if (checked) next.add (root.key)
else next.delete (root.key)
update (selectionKey, fields.filter (field => next.has (field.key))
.map (field => field.key).join (' '))
return
if (checked) next.add (key)
else next.delete (key)
}
if (checked)
{
const parts = path.split ('.')
for (let index = 1; index < parts.length; ++index)
selected.add (parts.slice (0, index + 1).join ('.'))
}
else selected.delete (path)
const nextParams = new URLSearchParams (params)
nextParams.set ('children', [...selected].filter (key => key.includes ('.')).join (' '))
const children = selectedCountChildren (nextParams)
const nextParams = new URLSearchParams (resolved)
nextParams.set ('children', [...next].filter (key => key.includes ('.')).join (' '))
const children = selectedCountChildren (nextParams, fieldsForCounts (resource, definition))
update ('children', [...children].join (' '))
const nextRoots = new Set (roots)
const ancestor = fields.find (field => path.startsWith (`${rootPath (field.key)}.`))
if (checked && ancestor != null) nextRoots.add (ancestor.key)
for (const field of fields)
if (field.children != null && !field.children.some (child =>
children.has (`${rootPath (field.key)}.${child.key}`))) nextRoots.delete (field.key)
update (selectionKey, fields.filter (field => nextRoots.has (field.key))
update (selectionKey, fields.filter (field => field.children == null
? next.has (rootPath (field.key)) : field.children.some (child =>
children.has (`${rootPath (field.key)}.${child.key}`)))
.map (field => field.key).join (' '))
}
const visibleFields = parent === ''
? fields.filter (field => field.key !== 'thumbnail') : fields
return (
<>
<FieldGroup fields={visibleFields} path={parent} params={params} enabled selected={selected}
<FieldGroup fields={visibleFields} path={parent} params={resolved} enabled selected={selected}
select={select} update={update}/>
</>)
}