@flytedan/flytebot-design-system 0.7.1 → 0.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/dist/index.d.cts CHANGED
@@ -924,6 +924,12 @@ interface SortMenuProps extends React.HTMLAttributes<HTMLSpanElement> {
924
924
  onSort?: (key: string, dir: "asc" | "desc") => void;
925
925
  /** Which edge the popover aligns to. Default "right". */
926
926
  align?: "left" | "right";
927
+ /** Trigger button height, same scale as every other control in the kit (Button,
928
+ * Input). Default "md" — matches Input/SearchField's own default height, since
929
+ * this is most often placed directly alongside a SearchField in one header row
930
+ * (see TransferList) and the two need to line up. Pass "sm" for a denser,
931
+ * standalone placement. */
932
+ size?: "sm" | "md";
927
933
  className?: string;
928
934
  }
929
935
  /**
@@ -941,7 +947,7 @@ interface SortMenuProps extends React.HTMLAttributes<HTMLSpanElement> {
941
947
  * Purely presentational: reports intent through onSort(key, dir) and never holds sort
942
948
  * state, so it stays honest against a real endpoint.
943
949
  */
944
- declare function SortMenu({ fields, sort, onSort, align, className, ...rest }: SortMenuProps): React.JSX.Element;
950
+ declare function SortMenu({ fields, sort, onSort, align, size, className, ...rest }: SortMenuProps): React.JSX.Element;
945
951
 
946
952
  interface Step {
947
953
  id: string;
@@ -1033,8 +1039,19 @@ interface VirtualListProps<T> {
1033
1039
  start?: boolean;
1034
1040
  end?: boolean;
1035
1041
  };
1036
- /** Suppresses onNeedMore while a fetch the caller already started is in flight. */
1042
+ /** Suppresses onNeedMore while a fetch the caller already started is in flight. Drives the
1043
+ * small pinned spinner at the bottom of the list — rows already mounted stay exactly as
1044
+ * they are, since this only ever means "more is being appended," never "what's on screen
1045
+ * might be stale." For the append-vs-replace distinction, see `refreshing` below. */
1037
1046
  loading?: boolean;
1047
+ /** True while the caller is replacing the WHOLE result set (a search or sort change), as
1048
+ * opposed to `loading` appending more onto the end. `items` is expected to still be the
1049
+ * PREVIOUS result set while this is true — this renders it underneath a full-panel scrim
1050
+ * instead of clearing to empty, so a search/sort edit never blanks the list while the new
1051
+ * page is in flight. Swap `items` to the new rows once they land and this back to false. */
1052
+ refreshing?: boolean;
1053
+ /** Copy shown in the `refreshing` overlay's centered card, next to its spinner. */
1054
+ refreshingLabel?: string;
1038
1055
  keyOf: (item: T) => string | number;
1039
1056
  /** Viewport height in px. Default (400) is ten rows at the kit's --control-h-md (44px). */
1040
1057
  height?: number;
@@ -1071,7 +1088,7 @@ interface VirtualListProps<T> {
1071
1088
  * needed to compute the window. The caller owns fetching more rows (onNeedMore) and
1072
1089
  * knowing when an edge is exhausted (hasMore).
1073
1090
  */
1074
- declare function VirtualList<T>({ items, itemHeight, renderItem, onNeedMore, hasMore, loading, keyOf, height, emptyState, className, style, }: VirtualListProps<T>): React.JSX.Element;
1091
+ declare function VirtualList<T>({ items, itemHeight, renderItem, onNeedMore, hasMore, loading, refreshing, refreshingLabel, keyOf, height, emptyState, className, style, }: VirtualListProps<T>): React.JSX.Element;
1075
1092
 
1076
1093
  interface EntityRowAction {
1077
1094
  /** Phosphor icon name, e.g. "plus" */
@@ -1089,35 +1106,45 @@ interface EntityRowProps {
1089
1106
  * title doesn't. Optional because plenty of rows are fine with just a name. */
1090
1107
  meta?: React.ReactNode;
1091
1108
  action?: EntityRowAction;
1092
- /** Marks the row a native HTML5 drag source. Wiring onDragOver/onDrop is the drop
1093
- * target's job (a list container, not each row) — see the file doc comment. */
1109
+ /** Marks the row a drag source: shows the grip icon and a grab cursor, and reports
1110
+ * `onPointerDown` so the caller's own pointer-based drag (see TransferList) can pick
1111
+ * the gesture up from there. Deliberately NOT the native HTML `draggable` attribute
1112
+ * (see the file doc comment for why) — this row never starts a native browser drag. */
1094
1113
  draggable?: boolean;
1095
- onDragStart?: (e: React.DragEvent<HTMLDivElement>) => void;
1096
- onDragEnd?: (e: React.DragEvent<HTMLDivElement>) => void;
1114
+ onPointerDown?: (e: React.PointerEvent<HTMLDivElement>) => void;
1097
1115
  style?: React.CSSProperties;
1098
1116
  className?: string;
1099
1117
  }
1100
1118
  /**
1101
- * EntityRow — a single compact row for a name plus an optional secondary line and an
1102
- * optional action, meant to be stamped out by the hundreds inside a VirtualList.
1119
+ * EntityRow — a single row for a name plus an optional secondary line and an optional
1120
+ * action, meant to be stamped out by the hundreds inside a VirtualList.
1103
1121
  *
1104
- * Deliberately thin, not a card: no border, no shadow, no internal padding beyond enough
1105
- * to keep text off the edges, so it reads as one line in a dense list rather than a tile.
1106
- * Height is left to the caller (it fills 100% of its parent) because VirtualList is what
1122
+ * A real card per row, not flat text: its own surface distinct from the list's
1123
+ * background (`--surface-2` against the list's `--surface`), a hairline border, a soft
1124
+ * rest-state shadow that lifts on hover the same `--r-md`/`--e-1`/`--e-2` elevation
1125
+ * vocabulary Card itself uses (see styles/components.css's ".fd-erow" rules), just at
1126
+ * the tighter radius a compact row reads better at than Card's own `--r-lg`. Height is
1127
+ * still left to the caller (it fills 100% of its parent) because VirtualList is what
1107
1128
  * actually fixes row height, and duplicating that number here would be one more place a
1108
- * future change has to remember to update.
1129
+ * future change has to remember to update — the card treatment lives entirely in
1130
+ * padding/border/shadow, not height.
1109
1131
  *
1110
1132
  * The drag/drop split is deliberate: a row only ever needs to say "I am the thing being
1111
- * dragged" (draggable + onDragStart/onDragEnd), never "something was dropped on me" —
1112
- * nothing in this kit drops one row onto another to reorder it, only a row onto a list to
1113
- * move it there. Putting onDragOver/onDrop on every row would mean wiring the same drop
1114
- * logic N times over instead of once on the list container that actually owns it (see
1115
- * TransferList), so this only exposes the source half of the native HTML5 DnD contract.
1133
+ * dragged" (draggable + onPointerDown), never "something was dropped on me" — nothing in
1134
+ * this kit drops one row onto another to reorder it, only a row onto a list to move it
1135
+ * there. Putting the drop handling on every row would mean wiring the same drop logic N
1136
+ * times over instead of once on the list container that actually owns it (see
1137
+ * TransferList's own doc comment for why it drives a pointer-based custom drag rather than
1138
+ * native HTML5 DnD — in short, a native drag image is a static snapshot, not a live
1139
+ * element, so it can't sustain a persistent tilt that follows the cursor), so this only
1140
+ * exposes the source half of the contract: `onPointerDown` hands the gesture off to
1141
+ * whatever the caller wants to do with it (TransferList starts tracking a drag once the
1142
+ * pointer has moved past a small threshold, so a plain click still reads as a click).
1116
1143
  * The action button exists precisely so drag-and-drop is never the only way to do the
1117
1144
  * same move — pointer-only and keyboard users get an equally real affordance, not a
1118
1145
  * degraded fallback.
1119
1146
  */
1120
- declare function EntityRow({ title, meta, action, draggable, onDragStart, onDragEnd, style, className }: EntityRowProps): React.JSX.Element;
1147
+ declare function EntityRow({ title, meta, action, draggable, onPointerDown, style, className }: EntityRowProps): React.JSX.Element;
1121
1148
 
1122
1149
  type TransferListSide = "left" | "right";
1123
1150
  interface TransferListSideProps<T> {
@@ -1126,6 +1153,11 @@ interface TransferListSideProps<T> {
1126
1153
  /** Total matching count on the server, if known — shown next to the label. */
1127
1154
  total?: number;
1128
1155
  loading?: boolean;
1156
+ /** True while this side's WHOLE result set is being replaced (a search or sort
1157
+ * change), not merely appended to. `items` should still be the previous result set
1158
+ * while this is true — see VirtualList's own `refreshing` prop, which this threads
1159
+ * straight through. */
1160
+ refreshing?: boolean;
1129
1161
  search: string;
1130
1162
  onSearchChange: (value: string) => void;
1131
1163
  sort?: {
@@ -1142,6 +1174,12 @@ interface TransferListSideProps<T> {
1142
1174
  /** Heading text for this side. Fully caller-supplied — this component has no opinion
1143
1175
  * about what the two sides represent. */
1144
1176
  label?: string;
1177
+ /** Copy shown in the full-panel drop overlay while a drag is over this side. Falls
1178
+ * back to a generic "Drop here" (plus `label`, if set) when omitted — set this for
1179
+ * exact per-side wording (e.g. "Drop to add to audience" / "Drop to remove from
1180
+ * audience"), since that phrasing is about what a drop here DOES, which only the
1181
+ * caller's domain actually knows. */
1182
+ dropLabel?: string;
1145
1183
  }
1146
1184
  interface TransferListProps<T> {
1147
1185
  left: TransferListSideProps<T>;
@@ -1169,19 +1207,23 @@ interface TransferListProps<T> {
1169
1207
  * lists is specific to that use, and baking in "available"/"selected" (or worse, this
1170
1208
  * kit's actual first consumer's own vocabulary) would make every *other* two-list picker
1171
1209
  * this kit will ever be asked for a second component instead of a second set of props. A
1172
- * caller wanting domain words passes them through `label`.
1210
+ * caller wanting domain words passes them through `label`/`dropLabel`.
1173
1211
  *
1174
- * Native HTML5 drag-and-drop, not a library: this kit ships zero runtime dependencies on
1175
- * purpose, and moving one row between two lists is exactly the case the platform's own
1176
- * draggable/dragstart/dragover/drop already cover with no gap to fillsortable-within-
1177
- * a-list (which does need pointer math and placeholder gaps) is a different, harder
1178
- * problem this component doesn't attempt. Each row is the drag source (via EntityRow's
1179
- * draggable prop); each *side* is the drop target one onDragOver/onDrop pair per side,
1180
- * not per row, since dropping only ever means "move into this list," never "onto this
1181
- * particular other row." dataTransfer carries the dragged key mainly so a real
1182
- * OS-level drag has real payload (Firefox refuses a drag with no data set at all); which
1183
- * item is actually moved is tracked in local `dragging` state, because dataTransfer's own
1184
- * payload can't hold a reference to `item: T` only the string the caller's keyOf gives it.
1212
+ * A pointer-based custom drag, not native HTML5 DnD, and not a library: this kit ships
1213
+ * zero runtime dependencies on purpose, and a pointer-events implementation (pointerdown/
1214
+ * pointermove/pointerup driving a portaled ghost element) is still zero-dependency just
1215
+ * more code than `draggable`. It replaced an earlier native-HTML5-DnD version once the
1216
+ * spec called for a real "card lifts out of the list and tilts, following the cursor"
1217
+ * feel: a native drag image (`dataTransfer.setDragImage`) is a static snapshot taken once
1218
+ * at dragstart, not a live element, so it can't sustain a persistent rotation that tracks
1219
+ * the pointer for the duration of the drag only owning the pointer events ourselves can.
1220
+ * Each row's pointerdown (via EntityRow's `onPointerDown`) arms a potential drag; nothing
1221
+ * visually happens until the pointer actually moves past `DRAG_THRESHOLD_PX`, so a plain
1222
+ * click (on the row, or on its +/- action button) is never mistaken for one. Once armed,
1223
+ * window-level pointermove/pointerup/pointercancel listeners (not per-row ones, since the
1224
+ * row being dragged gets replaced by a placeholder mid-drag) track the gesture to
1225
+ * completion; which side the pointer is currently over is decided by hit-testing each
1226
+ * side's own bounding rect against the pointer position, not by any per-row target.
1185
1227
  *
1186
1228
  * The +/- action on every row is not a fallback bolted on for accessibility box-ticking:
1187
1229
  * it is the same onMove call a drop makes, exposed as a first-class equivalent, because
package/dist/index.d.ts CHANGED
@@ -924,6 +924,12 @@ interface SortMenuProps extends React.HTMLAttributes<HTMLSpanElement> {
924
924
  onSort?: (key: string, dir: "asc" | "desc") => void;
925
925
  /** Which edge the popover aligns to. Default "right". */
926
926
  align?: "left" | "right";
927
+ /** Trigger button height, same scale as every other control in the kit (Button,
928
+ * Input). Default "md" — matches Input/SearchField's own default height, since
929
+ * this is most often placed directly alongside a SearchField in one header row
930
+ * (see TransferList) and the two need to line up. Pass "sm" for a denser,
931
+ * standalone placement. */
932
+ size?: "sm" | "md";
927
933
  className?: string;
928
934
  }
929
935
  /**
@@ -941,7 +947,7 @@ interface SortMenuProps extends React.HTMLAttributes<HTMLSpanElement> {
941
947
  * Purely presentational: reports intent through onSort(key, dir) and never holds sort
942
948
  * state, so it stays honest against a real endpoint.
943
949
  */
944
- declare function SortMenu({ fields, sort, onSort, align, className, ...rest }: SortMenuProps): React.JSX.Element;
950
+ declare function SortMenu({ fields, sort, onSort, align, size, className, ...rest }: SortMenuProps): React.JSX.Element;
945
951
 
946
952
  interface Step {
947
953
  id: string;
@@ -1033,8 +1039,19 @@ interface VirtualListProps<T> {
1033
1039
  start?: boolean;
1034
1040
  end?: boolean;
1035
1041
  };
1036
- /** Suppresses onNeedMore while a fetch the caller already started is in flight. */
1042
+ /** Suppresses onNeedMore while a fetch the caller already started is in flight. Drives the
1043
+ * small pinned spinner at the bottom of the list — rows already mounted stay exactly as
1044
+ * they are, since this only ever means "more is being appended," never "what's on screen
1045
+ * might be stale." For the append-vs-replace distinction, see `refreshing` below. */
1037
1046
  loading?: boolean;
1047
+ /** True while the caller is replacing the WHOLE result set (a search or sort change), as
1048
+ * opposed to `loading` appending more onto the end. `items` is expected to still be the
1049
+ * PREVIOUS result set while this is true — this renders it underneath a full-panel scrim
1050
+ * instead of clearing to empty, so a search/sort edit never blanks the list while the new
1051
+ * page is in flight. Swap `items` to the new rows once they land and this back to false. */
1052
+ refreshing?: boolean;
1053
+ /** Copy shown in the `refreshing` overlay's centered card, next to its spinner. */
1054
+ refreshingLabel?: string;
1038
1055
  keyOf: (item: T) => string | number;
1039
1056
  /** Viewport height in px. Default (400) is ten rows at the kit's --control-h-md (44px). */
1040
1057
  height?: number;
@@ -1071,7 +1088,7 @@ interface VirtualListProps<T> {
1071
1088
  * needed to compute the window. The caller owns fetching more rows (onNeedMore) and
1072
1089
  * knowing when an edge is exhausted (hasMore).
1073
1090
  */
1074
- declare function VirtualList<T>({ items, itemHeight, renderItem, onNeedMore, hasMore, loading, keyOf, height, emptyState, className, style, }: VirtualListProps<T>): React.JSX.Element;
1091
+ declare function VirtualList<T>({ items, itemHeight, renderItem, onNeedMore, hasMore, loading, refreshing, refreshingLabel, keyOf, height, emptyState, className, style, }: VirtualListProps<T>): React.JSX.Element;
1075
1092
 
1076
1093
  interface EntityRowAction {
1077
1094
  /** Phosphor icon name, e.g. "plus" */
@@ -1089,35 +1106,45 @@ interface EntityRowProps {
1089
1106
  * title doesn't. Optional because plenty of rows are fine with just a name. */
1090
1107
  meta?: React.ReactNode;
1091
1108
  action?: EntityRowAction;
1092
- /** Marks the row a native HTML5 drag source. Wiring onDragOver/onDrop is the drop
1093
- * target's job (a list container, not each row) — see the file doc comment. */
1109
+ /** Marks the row a drag source: shows the grip icon and a grab cursor, and reports
1110
+ * `onPointerDown` so the caller's own pointer-based drag (see TransferList) can pick
1111
+ * the gesture up from there. Deliberately NOT the native HTML `draggable` attribute
1112
+ * (see the file doc comment for why) — this row never starts a native browser drag. */
1094
1113
  draggable?: boolean;
1095
- onDragStart?: (e: React.DragEvent<HTMLDivElement>) => void;
1096
- onDragEnd?: (e: React.DragEvent<HTMLDivElement>) => void;
1114
+ onPointerDown?: (e: React.PointerEvent<HTMLDivElement>) => void;
1097
1115
  style?: React.CSSProperties;
1098
1116
  className?: string;
1099
1117
  }
1100
1118
  /**
1101
- * EntityRow — a single compact row for a name plus an optional secondary line and an
1102
- * optional action, meant to be stamped out by the hundreds inside a VirtualList.
1119
+ * EntityRow — a single row for a name plus an optional secondary line and an optional
1120
+ * action, meant to be stamped out by the hundreds inside a VirtualList.
1103
1121
  *
1104
- * Deliberately thin, not a card: no border, no shadow, no internal padding beyond enough
1105
- * to keep text off the edges, so it reads as one line in a dense list rather than a tile.
1106
- * Height is left to the caller (it fills 100% of its parent) because VirtualList is what
1122
+ * A real card per row, not flat text: its own surface distinct from the list's
1123
+ * background (`--surface-2` against the list's `--surface`), a hairline border, a soft
1124
+ * rest-state shadow that lifts on hover the same `--r-md`/`--e-1`/`--e-2` elevation
1125
+ * vocabulary Card itself uses (see styles/components.css's ".fd-erow" rules), just at
1126
+ * the tighter radius a compact row reads better at than Card's own `--r-lg`. Height is
1127
+ * still left to the caller (it fills 100% of its parent) because VirtualList is what
1107
1128
  * actually fixes row height, and duplicating that number here would be one more place a
1108
- * future change has to remember to update.
1129
+ * future change has to remember to update — the card treatment lives entirely in
1130
+ * padding/border/shadow, not height.
1109
1131
  *
1110
1132
  * The drag/drop split is deliberate: a row only ever needs to say "I am the thing being
1111
- * dragged" (draggable + onDragStart/onDragEnd), never "something was dropped on me" —
1112
- * nothing in this kit drops one row onto another to reorder it, only a row onto a list to
1113
- * move it there. Putting onDragOver/onDrop on every row would mean wiring the same drop
1114
- * logic N times over instead of once on the list container that actually owns it (see
1115
- * TransferList), so this only exposes the source half of the native HTML5 DnD contract.
1133
+ * dragged" (draggable + onPointerDown), never "something was dropped on me" — nothing in
1134
+ * this kit drops one row onto another to reorder it, only a row onto a list to move it
1135
+ * there. Putting the drop handling on every row would mean wiring the same drop logic N
1136
+ * times over instead of once on the list container that actually owns it (see
1137
+ * TransferList's own doc comment for why it drives a pointer-based custom drag rather than
1138
+ * native HTML5 DnD — in short, a native drag image is a static snapshot, not a live
1139
+ * element, so it can't sustain a persistent tilt that follows the cursor), so this only
1140
+ * exposes the source half of the contract: `onPointerDown` hands the gesture off to
1141
+ * whatever the caller wants to do with it (TransferList starts tracking a drag once the
1142
+ * pointer has moved past a small threshold, so a plain click still reads as a click).
1116
1143
  * The action button exists precisely so drag-and-drop is never the only way to do the
1117
1144
  * same move — pointer-only and keyboard users get an equally real affordance, not a
1118
1145
  * degraded fallback.
1119
1146
  */
1120
- declare function EntityRow({ title, meta, action, draggable, onDragStart, onDragEnd, style, className }: EntityRowProps): React.JSX.Element;
1147
+ declare function EntityRow({ title, meta, action, draggable, onPointerDown, style, className }: EntityRowProps): React.JSX.Element;
1121
1148
 
1122
1149
  type TransferListSide = "left" | "right";
1123
1150
  interface TransferListSideProps<T> {
@@ -1126,6 +1153,11 @@ interface TransferListSideProps<T> {
1126
1153
  /** Total matching count on the server, if known — shown next to the label. */
1127
1154
  total?: number;
1128
1155
  loading?: boolean;
1156
+ /** True while this side's WHOLE result set is being replaced (a search or sort
1157
+ * change), not merely appended to. `items` should still be the previous result set
1158
+ * while this is true — see VirtualList's own `refreshing` prop, which this threads
1159
+ * straight through. */
1160
+ refreshing?: boolean;
1129
1161
  search: string;
1130
1162
  onSearchChange: (value: string) => void;
1131
1163
  sort?: {
@@ -1142,6 +1174,12 @@ interface TransferListSideProps<T> {
1142
1174
  /** Heading text for this side. Fully caller-supplied — this component has no opinion
1143
1175
  * about what the two sides represent. */
1144
1176
  label?: string;
1177
+ /** Copy shown in the full-panel drop overlay while a drag is over this side. Falls
1178
+ * back to a generic "Drop here" (plus `label`, if set) when omitted — set this for
1179
+ * exact per-side wording (e.g. "Drop to add to audience" / "Drop to remove from
1180
+ * audience"), since that phrasing is about what a drop here DOES, which only the
1181
+ * caller's domain actually knows. */
1182
+ dropLabel?: string;
1145
1183
  }
1146
1184
  interface TransferListProps<T> {
1147
1185
  left: TransferListSideProps<T>;
@@ -1169,19 +1207,23 @@ interface TransferListProps<T> {
1169
1207
  * lists is specific to that use, and baking in "available"/"selected" (or worse, this
1170
1208
  * kit's actual first consumer's own vocabulary) would make every *other* two-list picker
1171
1209
  * this kit will ever be asked for a second component instead of a second set of props. A
1172
- * caller wanting domain words passes them through `label`.
1210
+ * caller wanting domain words passes them through `label`/`dropLabel`.
1173
1211
  *
1174
- * Native HTML5 drag-and-drop, not a library: this kit ships zero runtime dependencies on
1175
- * purpose, and moving one row between two lists is exactly the case the platform's own
1176
- * draggable/dragstart/dragover/drop already cover with no gap to fillsortable-within-
1177
- * a-list (which does need pointer math and placeholder gaps) is a different, harder
1178
- * problem this component doesn't attempt. Each row is the drag source (via EntityRow's
1179
- * draggable prop); each *side* is the drop target one onDragOver/onDrop pair per side,
1180
- * not per row, since dropping only ever means "move into this list," never "onto this
1181
- * particular other row." dataTransfer carries the dragged key mainly so a real
1182
- * OS-level drag has real payload (Firefox refuses a drag with no data set at all); which
1183
- * item is actually moved is tracked in local `dragging` state, because dataTransfer's own
1184
- * payload can't hold a reference to `item: T` only the string the caller's keyOf gives it.
1212
+ * A pointer-based custom drag, not native HTML5 DnD, and not a library: this kit ships
1213
+ * zero runtime dependencies on purpose, and a pointer-events implementation (pointerdown/
1214
+ * pointermove/pointerup driving a portaled ghost element) is still zero-dependency just
1215
+ * more code than `draggable`. It replaced an earlier native-HTML5-DnD version once the
1216
+ * spec called for a real "card lifts out of the list and tilts, following the cursor"
1217
+ * feel: a native drag image (`dataTransfer.setDragImage`) is a static snapshot taken once
1218
+ * at dragstart, not a live element, so it can't sustain a persistent rotation that tracks
1219
+ * the pointer for the duration of the drag only owning the pointer events ourselves can.
1220
+ * Each row's pointerdown (via EntityRow's `onPointerDown`) arms a potential drag; nothing
1221
+ * visually happens until the pointer actually moves past `DRAG_THRESHOLD_PX`, so a plain
1222
+ * click (on the row, or on its +/- action button) is never mistaken for one. Once armed,
1223
+ * window-level pointermove/pointerup/pointercancel listeners (not per-row ones, since the
1224
+ * row being dragged gets replaced by a placeholder mid-drag) track the gesture to
1225
+ * completion; which side the pointer is currently over is decided by hit-testing each
1226
+ * side's own bounding rect against the pointer position, not by any per-row target.
1185
1227
  *
1186
1228
  * The +/- action on every row is not a fallback bolted on for accessibility box-ticking:
1187
1229
  * it is the same onMove call a drop makes, exposed as a first-class equivalent, because