refactor(chips): share the chip-overflow measure core between the two lanes (S3)
ActiveFilterChips (feed applied-filter row) and ChannelTagsControl both hand-rolled the same single-line chip-fit measurer: a hidden mirror + a layout-effect + ResizeObserver reading chip right edges, the real rem gap, the pill width and the lane width. The policy half was already extracted (decideChipLayout); the DOM-measurement half was still duplicated. Promote that half to useChipOverflowFit(rowRef, chipsRef, pillRef, deps, decide, initial): it owns the effect + observer + initial measure and reads the common geometry, then hands it to each caller's own policy. The feed row layers its leading label + trailing "Clear all" reservation on top and keeps decideChipLayout; the tag cell's chips-only policy is extracted as fitChipsWithPill (pure, unit-tested) alongside it. Add a jsdom test for the hook (stubbed offsets, re-measure on deps). Behaviour unchanged — verified in the browser: feed all/count modes + popover, tag "+N" fit + hover reveal.
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
// Pure fit decision for a single-line chip lane that must share its width with fixed trailing
|
||||
// controls (the feed's applied-filter row + Sort/View). Lifted out of ActiveFilterChips so the
|
||||
// all/some/count math is unit-testable without a layout engine (jsdom reports 0-width boxes), the
|
||||
// same split as lib/dateFilter and lib/columnSort. The caller measures real widths off a hidden
|
||||
// mirror (including the live rem gap, so nothing is frozen in px) and hands them here.
|
||||
//
|
||||
// A sibling one-line-fit measurer lives in ChannelTags (ChannelTagsControl); if a third appears,
|
||||
// promote the DOM-measurement half to a shared hook and keep this as its policy core.
|
||||
// Pure fit decisions for a single-line chip lane that collapses its overflow into a "+N" pill.
|
||||
// Lifted out of the components so the math is unit-testable without a layout engine (jsdom reports
|
||||
// 0-width boxes), the same split as lib/dateFilter and lib/columnSort. The shared DOM-measurement
|
||||
// half — the layout-effect + ResizeObserver that reads real rem boxes off a hidden mirror — lives in
|
||||
// lib/useChipOverflowFit; it hands the measured geometry to one of these policies:
|
||||
// - decideChipLayout: the feed's applied-filter row, which also shares its width with fixed trailing
|
||||
// controls (Sort/View), reserves a leading label + a trailing "Clear all", and collapses to a
|
||||
// single count pill on a narrow lane.
|
||||
// - fitChipsWithPill: the chips-only channel tag cell (ChannelTagsControl) — just "how many fit
|
||||
// before the +N pill".
|
||||
|
||||
export interface ChipFitInput {
|
||||
/** Number of chips. */
|
||||
@@ -51,3 +53,37 @@ export function decideChipLayout(p: ChipFitInput): ChipFit {
|
||||
if (k === 0 && labelRight + gap + plusW > avail) return { mode: "count", visible: 0 };
|
||||
return { mode: "some", visible: k };
|
||||
}
|
||||
|
||||
// The chips-only policy (ChannelTagsControl): how many chips fit on the line, reserving the "+N" pill's
|
||||
// width whenever any overflow. Returns the visible count.
|
||||
export interface ChipsWithPillInput {
|
||||
/** Right edge (offsetLeft + offsetWidth) of each chip, index-aligned. */
|
||||
chipRights: number[];
|
||||
/** Width of the "+N" pill. */
|
||||
pillW: number;
|
||||
/** Available lane width. */
|
||||
avail: number;
|
||||
/** The real (rem-scaled) inter-item gap. */
|
||||
gap: number;
|
||||
}
|
||||
|
||||
export function fitChipsWithPill(p: ChipsWithPillInput): number {
|
||||
const { chipRights, pillW, avail, gap } = p;
|
||||
const n = chipRights.length;
|
||||
if (n === 0) return 0;
|
||||
// Whole-set fast path: if the LAST chip's right edge already fits, every chip fits and NO pill is
|
||||
// needed. The reservation loop below reserves the pill for every chip except the last and breaks at
|
||||
// the first overflow, so on its own it would collapse a short trailing chip (narrower than the pill)
|
||||
// that would actually have fitted, leaving "+N" beside empty space.
|
||||
if ((chipRights[n - 1] ?? 0) <= avail) return n;
|
||||
let count = n;
|
||||
for (let i = 0; i < n; i++) {
|
||||
const right = chipRights[i] ?? 0;
|
||||
const moreAfter = i < n - 1;
|
||||
if (right > avail - (moreAfter ? pillW + gap : 0)) {
|
||||
count = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user