toga-ai 1.0.595 → 1.0.596
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.
|
@@ -55,6 +55,13 @@ Constants in `PrimaryTable.tsx`: `DEFAULT_MIN_COLUMN_WIDTH = 80`, `ABSOLUTE_MIN_
|
|
|
55
55
|
|
|
56
56
|
1. **Measure** — on first paint, measure header + widest body cell content per column
|
|
57
57
|
(`measureCellContentWidth`), store in `frozenWidths`; per-column floors in `contentFloors`.
|
|
58
|
+
The **full header width is a HARD floor for every column** (`headerFloors` state): it is not
|
|
59
|
+
capped by max-width, not overridden by a consumer `minColumnWidth`, and is applied even to
|
|
60
|
+
explicitly-pinned (`columnWidths`) columns — the earlier `return def.size` bypass was why
|
|
61
|
+
pinned columns stayed clipped. Header width is measured **deterministically** = label text
|
|
62
|
+
`scrollWidth` + a fixed ~34px reserve per sort/filter control, **not** by measuring the
|
|
63
|
+
icon-font/SVG control elements (which can report ~0 width pre-paint on the freeze pass,
|
|
64
|
+
causing both header abbreviation and icon overlap).
|
|
58
65
|
2. **Freeze** — apply `table-layout: fixed` + a `<colgroup>` of frozen pixel widths so columns
|
|
59
66
|
don't reflow while virtualizing. Resets when the leaf column set changes. Empty tables stay
|
|
60
67
|
on auto-layout.
|
|
@@ -78,6 +85,10 @@ Sticky columns use a `--sticky-offset` CSS variable summed from neighboring colu
|
|
|
78
85
|
## Gotchas
|
|
79
86
|
|
|
80
87
|
- Frozen widths measure only **mounted** rows; virtualized tables size from the first paint set.
|
|
88
|
+
- **Header labels must never abbreviate or overlap.** The full header width is a hard floor
|
|
89
|
+
(above `maxColumnWidth`, `minColumnWidth`, and pinned `columnWidths`). Measure header width
|
|
90
|
+
deterministically (label `scrollWidth` + fixed per-control reserve), never by measuring the
|
|
91
|
+
async-loading sort/filter icon elements — they can report ~0 width before they paint.
|
|
81
92
|
- `headerSpan` adds a separate top row (`colSpan`); only the leaf header row maps to colgroup.
|
|
82
93
|
- Resize is non-persistent (local state, resets when columns change).
|
|
83
94
|
- **A visible column can look "missing" under `shrinkColumns`.** `shrinkColumns` (modal tables)
|
|
@@ -87,6 +98,11 @@ Sticky columns use a `--sticky-offset` CSS variable summed from neighboring colu
|
|
|
87
98
|
not the column set.
|
|
88
99
|
|
|
89
100
|
## Change history
|
|
101
|
+
- 2026-08-17 — Fixed clipped/abbreviated headers: full header width is now a hard floor for every
|
|
102
|
+
column (not capped by max-width, not overridden by `minColumnWidth`, applied even to pinned
|
|
103
|
+
`columnWidths` — the old `return def.size` bypass left pinned columns clipped). Added a
|
|
104
|
+
`headerFloors` state; header width is now measured deterministically (label `scrollWidth` +
|
|
105
|
+
fixed ~34px per sort/filter control) instead of measuring async-loading icon elements. (apeterson)
|
|
90
106
|
- 2026-08-17 — Noted that under `shrinkColumns` a genuinely visible column can render at a sliver width and look "missing" — check `isVisible` and the measured/frozen width, not the column set (apeterson).
|
|
91
107
|
- 2026-06-23 — Documented the three Primary Table entry points, measure-and-freeze sizing, virtualization, and sticky columns (apeterson).
|
|
92
108
|
- 2026-06-22 — Added `shrinkColumns` / `minColumnWidth` shrink-to-fit for modal tables; freeze logic uses natural content width (apeterson).
|
|
@@ -88,13 +88,18 @@ isHovered, skin, isDisabled }`. They are inserted as columns via `buildActionCol
|
|
|
88
88
|
|
|
89
89
|
Per-column header controls, each a popover trigger with `data-active` / `data-open`:
|
|
90
90
|
|
|
91
|
-
- **`HeaderFilterSearch`** — text filter,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
91
|
+
- **`HeaderFilterSearch`** — text filter, **multi-chip with per-chip mode**. Each confirmed
|
|
92
|
+
search becomes its own `TextFilterTag = { value, mode }` (mode ∈ Includes / Starts with /
|
|
93
|
+
Ends with / Exactly / Excludes). The mode dropdown sets the mode for the **next** chip
|
|
94
|
+
added; existing chips keep the mode they were created with. All chips on a column are
|
|
95
|
+
combined with **AND**. `onFilterChange` emits the full tags array (`{ tags }`); the
|
|
96
|
+
component rehydrates every chip from props and renders each as "Mode: value". `isActive =
|
|
97
|
+
tags.length > 0`. The mode dropdown is portalled to `document.body` with fixed positioning
|
|
98
|
+
recomputed on scroll. The search icon (solid when active, regular otherwise) is hidden
|
|
99
|
+
while the input is focused; both the trigger button and the icon span carry `data-active`.
|
|
100
|
+
The clear button must not widen the input row (fixed-width menu).
|
|
101
|
+
(Previously a column allowed only one term / one mode — a second search replaced the first
|
|
102
|
+
and switching mode re-applied it to everything.)
|
|
98
103
|
- **`HeaderFilterRange`** — numeric, modes exactly/moreThan/lessThan + a range toggle
|
|
99
104
|
(min/max); emits `{ key, value }[]` (`eq`/`min`/`max`/`between`). Reads its persisted
|
|
100
105
|
operator/values back from a `filterParams` prop (not `filterValue`, which collapses to the
|
|
@@ -134,6 +139,10 @@ Per-column header controls, each a popover trigger with `data-active` / `data-op
|
|
|
134
139
|
## Gotchas
|
|
135
140
|
|
|
136
141
|
- `MIN_COLUMN_WIDTH = 120` is the shrink floor only for columns with **no** explicit width.
|
|
142
|
+
- **Never truncate header labels.** `truncateCells` (`.truncateCells .headerCellLabel` in
|
|
143
|
+
`toga.module.css`) is for **body** values only; it must apply only `white-space: nowrap` to
|
|
144
|
+
the header (no `overflow:hidden`/`text-overflow:ellipsis`/wrapping) or headers show ellipses.
|
|
145
|
+
Headers must render in full on one line, never abbreviated, never overlapping.
|
|
137
146
|
- The filter `includes` mode treats comma-separated values as OR.
|
|
138
147
|
- `EditableCell` currency formats **on blur**, phone formats **on keystroke**.
|
|
139
148
|
- Cell type config (`columnTypeConfig` prop) overrides the hardcoded `COLUMN_TYPE_CONFIG`.
|
|
@@ -151,24 +160,36 @@ Per-column header controls, each a popover trigger with `data-active` / `data-op
|
|
|
151
160
|
never for data fetching. A visible column can still *look* missing when `shrinkColumns` freezes
|
|
152
161
|
it to a tiny width because its measured header+body content is minimal (see
|
|
153
162
|
[primary-table-templates](primary-table-templates.md)).
|
|
154
|
-
- **Filter presentational components must seed their internal
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
(
|
|
163
|
+
- **Filter presentational components must seed their internal state from props.**
|
|
164
|
+
Filter state survives the URL round-trip and is rehydrated into TanStack `columnFilters` by
|
|
165
|
+
toga25-supply `useServerTableUrlState`, carried through `PrimaryTableHeaderCell`. But
|
|
166
|
+
`HeaderFilterSearch`/`HeaderFilterRange` are remounted fresh each time a popover opens, so
|
|
167
|
+
they must seed local state from props or reopening *shows the wrong state*.
|
|
168
|
+
**Text (`HeaderFilterSearch`)** now persists as a single readable URL param
|
|
169
|
+
`<slug>_<col>_filter` = comma-separated `mode:value` pairs (values URL-encoded so `:`/`,`
|
|
170
|
+
inside a term can't corrupt the delimiters); `getDataTableData` parses it and pushes **one
|
|
171
|
+
AND WHERE condition per chip** (mode→op: includes→contains, startsWith→starts,
|
|
172
|
+
endsWith→ends, exactly→eq, excludes→excludes). The component rehydrates the full `tags`
|
|
173
|
+
array from props. Old base64/legacy single-mode `_starts`/`_ends`/… URLs don't parse under
|
|
174
|
+
the new scheme (graceful — just re-apply the filter). The browser may percent-encode `:`/`,`
|
|
175
|
+
in the address bar but it round-trips.
|
|
176
|
+
**Range (`HeaderFilterRange`)** still persists as key suffixes (`_min`/`_max`/`_eq`/
|
|
177
|
+
`_between`); it reads mode + value(s) back from `filterParams` via a rehydration effect keyed
|
|
178
|
+
on `JSON.stringify(filterParams)` (`eq`→Exactly, `min`→MoreThan, `max`→LessThan,
|
|
179
|
+
`between`→range min/max).
|
|
166
180
|
- **Table/surface meta is cached with `Infinity` staleTime/gcTime** (`useFetchTablePageMeta`), so
|
|
167
181
|
after a DB metadata change (`TableViewFields`, `Core.RecordFields`) a **hard reload** is required
|
|
168
182
|
to see it. And the app consumes toga-blox's built **`dist/`**, not `src/` — editing blox source
|
|
169
183
|
requires `npm run build` before it takes effect at runtime.
|
|
170
184
|
|
|
171
185
|
## Change history
|
|
186
|
+
- 2026-08-17 — Built multi-chip text column filter: each `HeaderFilterSearch` search is now its
|
|
187
|
+
own `TextFilterTag {value, mode}`, the mode dropdown sets the mode for the next chip, existing
|
|
188
|
+
chips keep their mode, and all chips AND together. Persisted as a single readable URL param
|
|
189
|
+
`<slug>_<col>_filter` = `mode:value,...` (URL-encoded values); `getDataTableData` emits one AND
|
|
190
|
+
WHERE per chip. Also fixed: header labels were being ellipsis-truncated (and overlapping the
|
|
191
|
+
next column) — `truncateCells` is body-only, so `.truncateCells .headerCellLabel` is now just
|
|
192
|
+
`white-space: nowrap`. (apeterson)
|
|
172
193
|
- 2026-08-17 — Fixed: column filter **mode** was not restored when a filter popover was reopened
|
|
173
194
|
(text reverted to `includes`, range to `exactly`). Root cause: the URL round-trip and
|
|
174
195
|
`PrimaryTableHeaderCell` preserved the mode, but `HeaderFilterSearch` (destructured
|
package/package.json
CHANGED