Repository navigation
feat(table): query-bar filter mode and a redesigned settings modal - #124
Merged
Merged
Conversation
…enField and flush appearance Host options for a bar that owns its chip list, the first step of the sd-table query-bar filter mode (.sdcorejs/handoff/table-query-bar). A bar that sets none of them behaves as before, except the two fixes below. - Fields: `pinned` (always on the bar, no remove button, out of the field picker, empty chip reads "All"), `required` (empty chip gets the error look, `aria-invalid` and `aria-describedby` = `option.invalidHintId`; Search is disabled; `invalidFields()` lists them), `group` (field-picker headings), and `disabled` is now honoured on the chip (no popover). The picker gets a find box above eight fields, matching labels without accents. - Option: `applyOn: 'commit'` applies a changed popover / inline commit, the removal of a chip with a value and Enter in the search box, drops an added chip that closes empty, and hides the Search button. `appearance: 'flush'`, `onClearAll`, `clearDisabled`, `searchPlaceholder`, `invalidHintId`, and `showOperatorOnChip: 'auto'` (input too). - `openField(key, returnFocusTo?)` opens a field's editor from outside the bar and can return focus to the caller. Slot `[sdQueryBarEnd]` after the toolbar. - Chip text: dates as dd/MM/yyyy (dd/MM/yyyy HH:mm), a range inside one year as `01/10 – 31/10/2026`, an open-ended range as "Từ …" / "Đến …". Values of a lazy-values chip (VALUE search or `option.views`) and of a values field with an items function are looked up once, so restored chips show labels. - Fixes: the clear-all label counts chips with a value, and reads without a count when nothing has one; a BETWEEN with both ends empty is an empty chip. - i18n (vi/en/ja/ko/zh): query-bar.all, .required, .find-field, .range.from, .range.to, .clear. Rolled out from v19 with npm run sync. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Base of the sd-table query-bar filter mode (.sdcorejs/handoff/table-query-bar,
sections 3–5). Nothing renders differently yet.
- `SdTableOptionFilter.mode` ('default' | 'query-bar') and `queryBar`
(`SdTableOptionFilterQueryBar`: `editor`, `pinned`, `exclude`), and the same
two keys in `ISdTableConfiguration.filter`.
- `TableFilterRegister.value.default()` (optional member): the value `remove()`
stores, read without writing it, for the "clear filters" state.
- `table-query-bar.util.ts` (not exported): `sdTableQueryFields` lists the bar
fields of a table (keys `quick:` / `external:` / `column:`, groups, pinned
chips, operators the raw record can hold, filters left out with a reason,
columns covered by an external filter of the same field);
`tableFilterToQuery` and `queryToTableFilter` translate between
`TableFilterValue` and `SdQuery`, keep chip identity, keep the stored form of
an equivalent value, and report changed fields for the callbacks.
- Spec: for 34 edits, `SdConvertToPagingReq` of the raw record written through
the bar equals the one of the default-mode controls, in the machine zone and
in Asia/Ho_Chi_Minh.
Rolled out from v19 with npm run sync.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With `filter.mode: 'query-bar'` (or SD_TABLE_CONFIGURATION.filter.mode) the table shows one sd-query-bar at the top of its surface instead of quick search, the external filter section and the column filter row (.sdcorejs/handoff/table-query-bar, sections 1–2, 6–7, 9–11). The bar is a view over the same TableFilterValue: reads, cache, `pagingReq`, `onFilter` and the filter callbacks are those of the default mode. - Mode: option > configuration > 'default'; mobile always renders the default controls. Host attribute `data-filter-mode` reflects what is rendered. `externalFilterPerRow`, `collapsible`, `externalFilterCollapsed` and `hideExternalFilterToolbar` are ignored in this mode. - Bar: flush appearance, role="search" labelled "Bộ lọc", data-autoid `<autoId>-query-bar`, quick-search placeholder, `sdTableQuickSearchRightDef` after its toolbar. Pinned: visible quick filters and inline / required external filters (`filter.queryBar.pinned` replaces the list); the rest is under "+", grouped "Bộ lọc" / "Cột". Excluded filters keep their values and are listed once per table in a dev-mode console warning. - Applying: every committed chip edit, chip removal and Enter applies in one `value.set` (an operator change on an empty column is stored without a read, as in the column filter); with `manualFilter` edits wait for the Search button. Column `filter.onChange`, `filter.operatorChange`, external and quick-filter `onChange` run after the write, only for user changes. - Raw → bar: chips keep their identity; outside changes (setFilter, a signal default, the cache) wait while a chip popover is open and do not overwrite unapplied edits of other chips. - Required: an empty required external filter blocks a server read through its value (no ExternalFilterComponent form in this mode) and is reported to `onFilter` as `externalFilterValid: false`; empty required chips are marked and a hint line under the bar names them. - Clear: resets to the defaults, calls `filter.onClearFilter`, reads once; disabled while the applied state equals the defaults. - A column hidden through the table settings keeps its chip while it still filters. - i18n (vi/en/ja/ko/zh): table.query-bar.filters, table.query-bar.columns. Rolled out from v19 with npm run sync. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In `filter.mode: 'query-bar'` every leaf column whose field is on the query bar, or is covered by an external filter of the same field, gets a 20 × 20 filter icon next to its title (.sdcorejs/handoff/table-query-bar, section 8). - Clicking it opens that field's chip on the bar, adding the chip when the field has none; it does not sort or start a column resize. When the popover closes, focus returns to the icon. - Hidden (opacity 0, still in the tab order) until the header cell is hovered or focused, or on a coarse pointer; always shown in the primary colour while the field filters by an applied value. `aria-label` "Lọc theo …" / "Đang lọc theo …", `aria-haspopup="dialog"`, data-autoid `<autoId>-filter-trigger-<field>`. - The icon sits beside `.c-header-title`, not inside it: a sortable title is role="button" and handles Enter, so a nested button would be nested interactive content and Enter on it would sort. Right-aligned columns put it before the title so the title edge does not move. - Grouped headers: the icon is in the leaf cell. - Reduced motion drops the fade; forced colors use ButtonText / Highlight. - i18n (vi/en/ja/ko/zh): table.filter-by, table.filtering-by. Rolled out from v19 with npm run sync. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Step 5 of the sd-table query-bar filter mode (.sdcorejs/handoff/table-query-bar, sections 17–18). - sd-table.md: "Query bar mode" under the filter option (options, layout, chips and the field mapping table, filters left off the bar, applying, required, clear, header icons, options ignored in this mode), the `data-filter-mode` host attribute, the query-bar and header-icon autoIds, and the configuration example. - CHANGELOG [Unreleased]: query bar host options, table query-bar mode, `TableFilterRegister.value.default()`, the clear-all count and the empty BETWEEN fix. - Showcase, Table → "Lọc bằng query bar": switches for `filter.mode` and `manualFilter`, a cached option with quick search (term, a required and a multiple dropdown), four external filters, operator / split-number / values / date columns, a column covered by an external filter and a grouped header, and the `pagingReq` the demo backend receives. Registry count 35 → 36. - The quick-search slot at the end of the bar no longer shrinks under its content and stays on the first chip row. - Screenshots of both modes, dark, graphite, forced colors, required, manual filter, refresh and mobile in .sdcorejs/handoff/screenshots. Rolled out from v19 with npm run sync. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r a reset
Two default-mode bugs found while building the query-bar filter mode.
Grouped headers: the filter pipeline only walked the top level of `columns`,
so a column under a `type: 'children'` header was invisible to it — its value
was left out of `pagingReq`, ignored by local filtering and sorting, and its
`filter.default`, cached value and SD_TABLE_CONFIGURATION operators were never
applied. A grouped header with a key in `rawColumnFilter` even sent its own
field. `leafColumns()` (services/column-leaf.util.ts) now feeds
`SdConvertToPagingReq`, `filterLocalItems`, the filter service defaults and
cache restore, and the operator merge of `#initConfiguration`. The filter
cache key is unchanged.
Split filters: clearing the filters (clearFilter, the external filter's clear
button, setFilter without the field) can drop the `{ from, to }` of a
split-number / split-date column from the shared columnFilter object, and the
split template threw "Cannot read properties of undefined (reading 'from')".
The template now reads `?.from` / `?.to` and writes through `setRangeEnd()`,
which recreates `{ from, to }` when it is gone and still writes in place
otherwise.
Specs, written first and seen failing (21 red): grouped-header-filter.spec.ts
(converter, local filter and sort, filter service), table.component
.grouped-filter.spec.ts (first request, setFilter, configuration operators,
clear and retype with split filters), and column-filter "split filter whose
range object disappears in place".
Docs: sd-table.md gains "Grouped headers and split filters", CHANGELOG
[Unreleased] gets a Fixed › Table entry, and the query-bar showcase demo
puts its money columns back under a grouped header.
Rolled out from v19 with npm run sync.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The columns tab of the table settings modal drops its five-column mat-table
for a list: one 44px row per column with the drag handle, the Show checkbox
and a button that opens the row. The summary shows the title, push_pin /
short_text flags and the width ("120 px", "Tự động", "Đang ẩn", or a non-px
value verbatim). The open row edits the title (with "Gốc: … · Khôi phục"),
the width and the fixed / truncate checkboxes; one row is open at a time and
a hidden column locks its details while its Show checkbox stays usable.
- Find box past eight columns, matching the current and the original title
without accents; reordering is off while searching and the hint says so.
A row the search filters out closes.
- "Hiện {shown}/{total} cột" and Show all.
- Width: a number is stored as `${n}px`, empty as undefined, a non-px width
is edited as text. Rounded and clamped to the column's px minWidth /
maxWidth on blur and on Apply (config-width.util.ts).
- Footer: Restore defaults moves to sdFooterLeft (text, restart_alt);
Skip is outline, Apply filled; all sm. AutoIds and the reset confirm are
unchanged; the find box and Show all get -config-find / -config-show-all.
- Edits replace the column object instead of mutating it, so Skip leaves the
stored value alone.
- i18n: 12 new core.component.table.config.* keys in five locales, new
drag-hint text. core.component.table.fixed / char-limit and
core.component.form-builder.default stay but the modal no longer uses them.
Handoff package and plan in .sdcorejs/handoff/table-settings/.
Rolled out from v19 with npm run sync.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The drag handle of each settings row now works from the keyboard. Space or Enter picks the row up, the arrow keys move it, Space or Enter drops it and Escape puts the order back. While a row is held, the keys do not reach the dialog, so Escape does not close it and the arrows do not scroll the list. Focus follows the moved handle after each step, and leaving the handle drops the row where it is. Reordering stays off while a search narrows the list. Each step is read out by a live region inside the dialog (aria-live="assertive"): picked, moved, dropped, cancelled, with the column title and its position. The handle is described by the drag hint. A held row is outlined in primary like the mouse drag preview. i18n: core.component.table.config.reorder-picked / -moved / -dropped / -cancelled in five locales. Rolled out from v19 with npm run sync. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ings
The footer settings button (-config) now carries a primary dot while the
table uses a setup of its own, through the existing sd-button indicator:
[indicator], indicatorColor="primary" and an indicatorLabel ("Đang dùng
thiết lập riêng") read through aria-describedby. Mobile shows no dot.
hasCustomSetup() is true when:
- the stored columns differ from the option in order, or from their stored
origin in visibility, title, width, fixed or truncate. A width dragged on
the table counts: persistColumnWidth writes the same storage entry, so the
table re-reads it on widthChange$. Columns added to or removed from the
option since the setup was stored are ignored, and comparing with the
stored origin keeps a title changed in code (or by language) from counting;
- the inline external filters differ from defaultShowing.
Without option.key it follows the session storage, as the table does.
Restore defaults turns the dot off at once: the settings modal now emits an
internal defaultsRestored output and the table puts the inline external
filters back to defaultShowing (the confirm text already says "all
settings").
services/config-custom.util.ts holds defaultConfiguredTable(), extracted from
ConfigService.#default (which now calls it), and sdTableHasCustomSetup().
ConfiguredColumn, ConfiguredTable, ConfigService and the storage format are
unchanged.
i18n: core.component.table.config.customized in five locales.
Rolled out from v19 with npm run sync.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- sd-table.md: new "Settings modal" section (list, open row, width rules, find box, keyboard reorder, footer) and "Settings button indicator"; config.visible row updated; -config-find / -config-show-all autoIds; an Accessibility note on the modal. - CHANGELOG [Unreleased]: Added › Table (settings button dot, keyboard reorder) and Changed › Table (columns tab list, footer, Restore defaults also restores the inline external filters, Skip no longer touches stored settings, i18n keys). - Showcase: "Thiết lập cột" demo, a 14-column table with key, resizable columns, a minWidth/maxWidth column and a 20% column; table registry count 37 (total 496), keyword "column settings"; example sources regenerated. - Settings modal: in forced colours a hidden column's title uses GrayText, found while checking the showcase. - Handoff plan: commits, deviations, API consistency notes for the PR and the showcase checks; screenshots 13-15 (1920px, graphite, forced colours). Rolled out from v19 with npm run sync. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- applyOn 'commit': a chip change applies the search text applied last; the text still being typed is applied by Enter, as the default-mode quick search does, so the same edits send the same pagingReq in both modes. The table keeps its applied term and leaves the typed text in the box when the bar leaves it out. Enter in the search box no longer applies while a required field is empty (applySearch()). - Inline mode renders a disabled field as the read-only chip; it could be edited before. - A hidden column filtering with NULL / NOT_NULL keeps its chip (and the clear button); a column covered by an external filter gets its NULL / NOT_NULL operator reset, so no invisible condition is sent. - A required external filter the bar cannot show (custom, filterDef, exclude, …) is named in the hint instead of blocking reads silently. - Chip identity compares chip data in its normalized shape, so a one-ended range committed by the popover keeps its chip (and focus). - Header filter trigger: aria-haspopup="menu" (the popover is a mat-menu). Specs written first and seen failing: query-bar (3), table query-bar (4), util (2). sd-table.md and CHANGELOG changes land with the next commit. Rolled out from v19 with npm run sync. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Pressing a handle closes only its own open row. Closing an open row above moved the pressed row away from the pointer before CDK measured, so the row landed in the wrong place. - The toolbar wraps in a narrow modal (count and Hiện tất cả form one group) instead of making the modal body scroll sideways. - Apply and blur round and clamp only edited widths, so an untouched `120.5px` or a width below the column's own minWidth no longer lights the settings dot. `-50` / `0` store undefined, `1e3` stores `1000px`. - Column resize: a click on the grip that does not change the width stores nothing and does not call config.onResize (it wrote the rendered width of auto / % columns before, which now also lit the dot). - Accessibility: each handle is named after its column, the fixed / shortened flags are named, aria-controls only while the row is open. - Specs for the mouse path: drop index mapping, drop while searching, pressing a handle. Docs: sd-table.md (settings modal, onResize, and the query-bar hint and trigger from the previous commit), sd-query-bar.md, CHANGELOG [Unreleased] (also drops the "Skip" bullet: SdStorageService.get() already returned a clone), both handoff PLAN.md files (review fixes, `defaultsRestored`). Rolled out from v19 with npm run sync. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two design handoffs for
sd-table, one commit per step, plus two default-mode bug fixes found on the way. Everything is written inversions/v19and rolled out to v20/v21/v22 withnpm run sync(check:syncgreen). Unreleased; CHANGELOG[Unreleased]is updated.1. Query-bar filter mode (
.sdcorejs/handoff/table-query-bar/)filter.mode: 'query-bar'(alsoSD_TABLE_CONFIGURATION.filter.mode) replaces quick search, the external filter section and the column filter row with onesd-query-barabove the table. The bar is a display layer only:TableFilterValue, the filter service,SdConvertToPagingReq,items(),filterReq,pagingReqand the cache format are unchanged, so the same edit sends the same request in both modes (the showcase demo printspagingReqto compare).eaa249cb0sd-query-bar:pinned/required/groupfields, lazyoption.views,applyOn,appearance: 'flush',onClearAll,clearDisabled,openField(),[sdQueryBarEnd], field-picker find box, date formatting on chips.3fe970aa9table optionfilter.mode/filter.queryBar, adaptertable-query-bar.util.ts(68 parity cases),TableFilterRegister.value.default().3256d0839the bar in the table: required,manualFilter, clear, cache, loading, mobile (default controls kept), dev warning for filters the bar cannot edit.3d9476da9filter icon in each filterable column header, opening that column's chip.9206a580fdocs (sd-table.md"Query bar mode"), CHANGELOG, showcase "Lọc bằng query bar".Default mode is unchanged (DOM, classes, autoIds, emit order); the host gets
data-filter-modein both modes.2. Default-mode fixes (
687343b99)type: 'children'header were never sent, matched locally, sorted, defaulted or restored from cache;leafColumns()now feedsSdConvertToPagingReq, local filter/sort, the filter service and the configuration operator merge. Cache key unchanged.clearFilter()with asplit-number/split-datecolumn threw in the column-filter template; it now reads?.from/?.toand writes throughsetRangeEnd().3. Settings modal (
.sdcorejs/handoff/table-settings/)103c54a9bColumns tab becomes a list: handle, Hiện checkbox, summary (title, fixed / shortened flags, width); a row opens to edit title (with “Gốc: … · Khôi phục”), width (number →${n}px, empty →undefined, other units verbatim, clamped to pxminWidth/maxWidthon blur and Apply) and the two checkboxes. Find box above 8 columns, “Hiện n/N cột”, Hiện tất cả. Footer: Khôi phục mặc định left, Bỏ qua outline, Áp dụng filled, allsm.79db5f374Keyboard reorder on the handle (Space/Enter, ↑/↓, Escape) with a live region inside the modal.a8402f3f7Primary dot on the footer settings button (sd-buttonindicator) while the table uses custom settings: stored columns that differ from the option (modal or a resize on the table), or inline external filters that differ fromdefaultShowing. Restore defaults turns it off; no dot on mobile.f4c49f320docs (sd-table.md"Settings modal"), CHANGELOG, showcase "Thiết lập cột" (14 columns); forced-colours title for hidden columns.Storage key and format,
ConfiguredColumn/ConfiguredTable/ConfiguredTableResult,ConfigService,loadConfigurationResult,open()/close()/onSave()/onReset()and the footer autoIds are unchanged.4. Independent review fixes
Two independent reviewers (query bar + fix; settings modal) read the branch on 2026-10-30. Every finding was verified against the code, then fixed with a failing spec first:
7696ebd54query bar: withapplyOn: 'commit'a chip change applies the search text applied last (the text still being typed is applied by Enter, as in default mode — mode parity); Enter waits for required fields; inline mode keepsdisabledfields read-only; a hidden column filtering with NULL / NOT_NULL keeps its chip and a covered column's NULL / NOT_NULL operator is reset; required external filters the bar cannot show are named in the hint; one-ended ranges keep their chip identity; header triggeraria-haspopup="menu".e3b971f88settings: pressing a handle closes only its own open row (dragging below an open row jumped); the toolbar wraps in a narrow modal; only edited widths are rounded / clamped (no dot after an Apply without edits;-50/0→undefined,1e3→1000px); a click on the resize grip without a change stores nothing; handle and flag names,aria-controlsonly while open; mouse-path specs; docs and CHANGELOG corrections.Behaviour changes to note
defaultShowing(the confirm text already says “all settings”).config.onResize(released feature; listed under Changed).core.component.table.fixed,char-limit,core.component.form-builder.defaultstay in i18n but the modal no longer uses them.API consistency review
sd-query-baroption callbacks (onApply/onQueryChange→ newonClearAll),SdTableExternalFilter(required/disabledmirrored on query fields),SdTableColumnLazyValues.option.views,sd-buttonindicator inputs,sd-input/sd-checkboxsize="sm"/hideInlineError/label,sd-modalfooter slots, existing table autoIds (-config-*,<B>-query-bar).filter.mode,filter.queryBar(option + configuration, option wins),TableFilterRegister.value.default()(optional), query-bar field/option knobs (option-only, per the “option is the main API” rule;showOperatorOnChipinput widened toboolean | 'auto', compatible),SdQueryBar.applySearch(), autoIds<B>-filter-trigger-<field>,-config-find(sd-input →forms-input-…),-config-show-all.SdTable.hasCustomSetupandSdTable.onConfigDefaultsRestoredare additive public members of the exported class (they appear in its.d.ts), named like the existingmobileCardscomputed andonColumnResizetemplate handler. Internal:ConfigComponent.defaultsRestored(not exported),config-custom.util.ts,column-leaf.util.ts.npm run sync; no hand edits in derived workspaces.isMobile/mobileCards,onColumnResize, internal outputs such asSdColumnResize.resizeEnd, existing-tree-toggle-<id>/-config-*autoIds). One naming note kept as is:SdQueryBarOption.appearance: 'default' | 'flush'reuses a word that meansMatFormFieldAppearanceon form controls, without conflict. Findings fixed in section 4.Deviations from the handoffs and their reasons are listed in each
PLAN.md.Verification
ng test sdcorejs-angular --code-coverage: 8669/8669 after the review fixes (Statements 87.48%, Branches 78.4%, Functions 86.49%, Lines 89.92%). Specs written first and seen failing for every step and every review finding.npm run lint,npm run test:theme(53/53),npm run check:scss-hex(0), i18n parity (1466 keys × 5 locales),npm run check:sync,test:showcase-changelog,test:showcase-examples, showcasedocs/corespecs (111/111).Screenshots
All screenshots:
.sdcorejs/handoff/screenshots/table-query-bar-*.pngandtable-settings-*.png.🤖 Generated with Claude Code