feat(filter-builder): multi-pick values render as removable chips

The id-kind value cells (feed/library/collection/added_by) rendered
their picks as one comma-joined string inside a 9rem-wide input — you
saw the first label and nothing else, and reading 3-5 picks meant
scrolling inside a tiny box (worst on mobile, user request 2026-11-01).

Id-kind cells now render a chipbox: each picked value is a chip (label
truncated with a hover title, removable via an X button with a finger-
sized tap target), the box flex-wraps and grows vertically instead of
clipping, and the inline input stays at the end for typing/searching —
including the Backspace-on-empty-removes-last-chip affordance. Text/
number/date cells keep today's single input; the value menu is
unchanged (it never offers an already-picked value, so the chip X is
the faster removal path, not a new one).

Presentation only: the stored wire is still one comma-joined value per
rule; matching, menus, apply, and label resolution are untouched. Pins
moved from valueCellText (now text-cells-only) to the chip model
(idChipsFor/idDraftFor) and cover removal order, last-chip removal, and
Backspace; 187 web tests green.
This commit is contained in:
John O'Keefe committed 2026-11-01 13:33:34 -05:00
1 parent d56031f334
commit a20741ccc9
6 files changed
+187 -27

No files matched your search

+22 -4
View File
@@ -59,10 +59,28 @@ templ FilterBuilderRows(countNoun string) {
</span>
</template>
<template x-if="hasValueCell(row.rule) && (valueArity(row.rule) === 'one' || valueArity(row.rule) === 'many')">
<span class="fb-value-cell relative flex items-center gap-1">
<input type="text" class="input fb-value" :value="valueCellText(row.path)" @input.debounce.250ms="onValueCellInput(row.path, $el.value)" @focus="openValueMenu(row.key, row.path, row.rule)" :placeholder="valuePlaceholderFor(row.rule)"/>
<template x-if="fieldIsDays(row.rule.field)">
<span class="text-xs whitespace-nowrap" style="color: var(--text-secondary)" x-text="'days'"></span>
<span class="fb-value-cell relative flex items-center gap-1" :class="isIdValueRow(row.path) ? 'fb-chipbox flex-wrap' : ''">
<template x-if="isIdValueRow(row.path)">
<span class="contents">
<!-- picked values as removable chips: the box
wraps and grows instead of a 9rem input
you can't read past the first entry -->
<template x-for="chip in idChipsFor(row.path)" :key="chip.id">
<span class="fb-chip" :title="chip.label">
<span class="fb-chip-label" x-text="chip.label"></span>
<button type="button" class="fb-chip-x" :aria-label="'Remove ' + chip.label" @click="removeIdChip(row.path, chip.id)">×</button>
</span>
</template>
<input type="text" class="fb-value fb-chip-input" :value="idDraftFor(row.path)" @input.debounce.250ms="onValueCellInput(row.path, $el.value)" @focus="openValueMenu(row.key, row.path, row.rule)" @keydown="chipInputKeydown(row.path, $event)" :placeholder="valuePlaceholderFor(row.rule)" :aria-label="fieldLabelOf(row.rule.field) + ' values'"/>
</span>
</template>
<template x-if="!isIdValueRow(row.path)">
<span class="contents">
<input type="text" class="input fb-value" :value="valueCellText(row.path)" @input.debounce.250ms="onValueCellInput(row.path, $el.value)" @focus="openValueMenu(row.key, row.path, row.rule)" :placeholder="valuePlaceholderFor(row.rule)"/>
<template x-if="fieldIsDays(row.rule.field)">
<span class="text-xs whitespace-nowrap" style="color: var(--text-secondary)" x-text="'days'"></span>
</template>
</span>
</template>
<!-- field-aware value menu: server JSON (the other
rules bound), or the field's fixed options.