Repository navigation
feat(table): loading layer without an overlay (skeleton rows, delayed progress bar) - #118
Merged
Merged
Conversation
Foundation of the sd-table loading layer (design handoff 1e, kept in
.sdcorejs/handoff/table-loading/). The table looks the same after this
commit; the next ones draw the new layer.
- SdTableOptionLoading (skeleton, skeletonRows, showDelay, minDuration,
dim) on option.loading and SD_TABLE_CONFIGURATION.loading, merged field
by field (option, configuration, defaults); invalid values count as
not set.
- loadingView() (host data-loading-view: none / skeleton / refresh),
derived from loading(): skeleton while a table without rows loads,
waits for its first read or for its configuration; refresh once rows
have been loading for showDelay, kept for minDuration unless the
result has no rows. A read that replaces the running one keeps a
visible bar through its debounce, but the debounce never starts it.
Timers run outside the zone and stop on destroy.
- loading(), data-loading and aria-busy are unchanged.
- span.sd-table-live, always in the DOM, announces "loading" when the
layer shows and "loaded {count}" (new key core.component.table.loaded
in the five locales) when the read ends.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…gress bar The dark overlay with a 100px spinner covered the header, filters and scrollbars and stopped 56px above the bottom whatever the footer was. Nothing covers the table any more: - No rows yet: skeleton rows under the real header, as rows of a matNoDataRow template, so they line up with the columns (grouped headers included) without measuring and the footer stays below. 5 rows, or enough to fill a fill-mode / style.maxHeight table, at most paginate.pageSize and 20. Bar widths follow the column type and align. Before the configuration is ready a generic 5 x 4 skeleton shows, and the empty block no longer flashes before the first read. - Rows on screen, after showDelay: body rows (not footers) dim to --sd-table-loading-dim and ignore the pointer; a 2px bar sits in .c-container right under the last header row (--sd-table-head-h, measured while it shows), so it does not scroll sideways. .c-container isolates the sticky z-indexes. - Tokens --sd-table-skeleton-bg, --sd-table-skeleton-highlight and --sd-table-progress-track (one color-mix for light and dark). Animations stop under prefers-reduced-motion; forced-colors uses system colours. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- No rows yet: up to three skeleton cards (skeletonRows, at most 3) in the same .sd-mobile-list, with the frame of .sd-mobile-card and three bars; the list drops role="list" while it only holds them. - Refresh: the list dims and ignores taps (scrolling and the keyboard still work); the 2px bar sits at the top of the card list, under the mobile command header, in a 0-height sticky anchor so it never pushes the cards. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tree toggle and the expand button used to be replaced by a 40px .lds-ring while their children or details loaded: keyboard focus fell back to the page, the row grew taller and the ring was a pale #cceeff. - Tree toggle (desktop): the button stays; while loading its chevron becomes a 16px CSS spinner (2px arc in --sd-table-spinner on --sd-table-progress-track), with aria-busy and aria-disabled. A still 25% arc under prefers-reduced-motion. - Expand (desktop): the 35px sd-button stays and shows its loading spinner ([loading], primary while loading), aria-busy on sd-button. onExpand / onTreeToggle already ignore clicks while loading. - Mobile tree and expand buttons swap hourglass_empty for the same 16px spinner and keep their disabled state. - Removed .lds-ring, @Keyframes lds-ring and .sd-tree-spinner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A local table whose items() throws or returns no array showed a line of text and a grey "Tải lại" button, unlike the sd-data-state error of a server table. It now uses the same sd-data-state error block (title, message, "Thử lại" calling reload()) and the same sdDataStateTemplate branch; hideReadError still hides it. The warning toast stays. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ontrols - sd-table.md: a Loading section (views, timings, SdTableOptionLoading, tokens, motion and forced colours), the loading option row, data-loading-view in the E2E attributes, the accessibility notes and the configuration provider example; the overlay and Material spinner lines are gone. - CHANGELOG [Unreleased]: the loading change in Before upgrading and Highlights, Added (option, attribute, i18n key, tokens), Changed and Removed (internal loading classes). - Showcase "Loading / empty / error": a fake-delay switch (0 / 150 / 600 / 3000 ms), a "Lần tới lỗi" toggle that fails the next read, and sorting on two columns; note, props and English description updated, example sources regenerated. - Before/after screenshots in .sdcorejs/handoff/screenshots/table-loading/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Dimmed rows still took clicks on sd-button (expand, group toggle, row commands, buttons in cells): sd-button sets pointer-events: auto on its host and inner button. The table body (tbody) now dims as one layer and blocks the pointer on every descendant; the same on mobile cards. This also stops nested tables from dimming twice and replaces one opacity transition per row with one per table. .sd-table-dim is .c-table--dim. - The progress bar no longer survives an option change (a held bar came back over the new rows), and a read that replaces the running one keeps the bar or the skeleton through its debounce even without rows, so the empty block no longer flashes (skeleton: false, after an empty result). - Timers stay outside the zone but run their callback inside it, so apps with ignoreChangesOutsideZone also render them. - The bar is measured after render when the table was not in the DOM yet (mobile to desktop during a refresh); z-index comments corrected. - A percentage style.maxHeight falls back to the default skeleton rows. - Status region: "loading" only once a read really runs (no "loaded N" on every page load for synchronous local tables), a new read clears an earlier count, tree tables count expanded rows, and a new key core.component.table.loaded-one (five locales) for one row. - Docs: data-loading-view in docs/E2E-ATTRIBUTES.md, sd-table.md wording (skeleton is immediate; focus kept on desktop), CHANGELOG bullets moved back under their release entry. Generic skeleton gets the table surface; showcase example sets option.loading explicitly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e tree toggle - The empty block (.c-no-data-row) loses role="status"/aria-live: its text was inserted together with the role, which screen readers often skip. The always-present .sd-table-live region now reads the title of the empty block (no-results, choose-filter-hint or no-data) when a read ends without rows, also after a read faster than showDelay and under a custom sdDataStateTemplate. - The desktop tree toggle gets aria-label (new key core.component.table.tree-toggle in the five locales) and aria-expanded, like the mobile toggle. - Specs, sd-table.md and CHANGELOG [Unreleased] updated; rolled out to v20-v22 with npm run sync. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 of 3 tasks
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.
Summary
Implements the sd-table loading design handoff (option 1e,
.sdcorejs/handoff/table-loading/). The dark overlay with a 100px spinner is gone: it covered the header, filters and scrollbars, stopped 56px above the bottom whatever the footer, and was nearly invisible in dark mode.<tr>/<td>of amatNoDataRowtemplate, so they line up with the columns without measuring; mobile cards show up to 3 skeleton cards.showDelay(300 ms) dims the table body to 55% (pointer input off, buttons in cells included; keyboard untouched) and shows a 2px progress bar under the last header row, kept for at leastminDuration(400 ms) when the new result has rows. Faster reads show nothing. A read that replaces the running one (sort, paging, filter) keeps an already visible bar through its debounce; the debounce alone never shows it.span.sd-table-liveis always in the DOM and reads "Đang tải dữ liệu bảng", then "Đã tải xong {count} dòng" (new keycore.component.table.loaded, five locales).aria-busy), so keyboard focus no longer falls back to the page;.lds-ringis removed.sd-data-stateblock andsdDataStateTemplateas server tables (retry =reload()); the warning toast stays.loading(),data-loading,aria-busy,readState,retryRead(),reload(), the paginator lock and the read logic.New API (additive):
SdTableOptionLoadingonoption.loadingandSD_TABLE_CONFIGURATION.loading(skeleton,skeletonRows,showDelay,minDuration,dim, merged field by field), host attributedata-loading-view(none/skeleton/refresh), tokens--sd-table-skeleton-bg,--sd-table-skeleton-highlight,--sd-table-progress-track, CSS variable--sd-table-loading-dim.Commits (one per step of the handoff)
feat(table): loading view state, option, status region (no visual change)feat(table): desktop skeleton rows, dimmed rows, progress bar, tokensfeat(table): mobile skeleton cards, dimming, progress barfeat(table): 16px spinner for tree and expand buttonsfeat(table): local read errors withsd-data-statedocs(table):sd-table.md, CHANGELOG[Unreleased], showcase controls, screenshotsfix(table): findings of the independent review (see below)fix(table): empty results announced through the status region; the desktop tree toggle gets a name andaria-expandedAll library changes start in
versions/v19and roll out withnpm run sync;check:syncpasses on every commit.API consistency review
Independent review done (two fresh reviewers given only the code, the spec and
AGENTS.md; findings verified by the author). It confirmed the naming, typing and merge order against the conventions below and found no break. Its findings are fixed in commit 7:sd-button(it setspointer-events: auto): the table body now dims as one layer and blocks the pointer on every descendant (mobile cards too); nested tables no longer dim twice.ignoreChangesOutsideZone); the bar is measured after render after a mobile → desktop switch; a percentagestyle.maxHeightno longer counts as pixels.core.component.table.loaded-one.data-loading-viewindocs/E2E-ATTRIBUTES.md,sd-table.mdwording, CHANGELOG bullets placed back under their entry.Two open points of the review are fixed in commit 8:
.c-no-data-rowblock wasrole="status"but inserted together with its text, which screen readers often skip. The block loses its live role; the always-present.sd-table-liveregion now reads the title of the empty block (no-results,choose-filter-hintorno-data) when a read ends without rows, also after a read faster thanshowDelayand under a customsdDataStateTemplate.aria-label(new keycore.component.table.tree-toggle, “Mở/đóng dòng con”, five locales) andaria-expanded, like the mobile one.Review notes on the API:
SdTableOptionPaginate/SdTableOptionReload/SdTableOptionExpand/SdTableOptionTree, the app-wideISdTableConfiguration.paginate/.filter/.images, the host attributesdata-autoid/data-loading, the i18n keyscore.component.table.*and the--sd-table-*tokens.SdTableOptionLoadinginmodels/table-option-loading.model.ts, exported throughmodels/index.tsliketable-option-tree.model;option.loadingsits next tooption.reload/option.paginate;ISdTableConfiguration.loadingmirrorspaginate/filter.paginate.pageSizeandfilter.*; invalid values count as not set.skeleton,dim), milliseconds (showDelay,minDuration) and a count (skeletonRows). No outputs or callbacks, so theon<Action>convention does not apply.option.loading(configuration) vs theloading()signal follows the existingoption.reloadvsreload()pair.SdTable(read-only signals or template data):loadingSettings,loadingView,liveMessage,skeleton,genericSkeleton.core.component.table.*:loaded,loaded-one,tree-toggle(the mobile toggle keepsmobile.expand-row, which names the row)..c-loading, the overlaymat-spinner,.lds-ring,@keyframes lds-ring,.sd-tree-spinner). Both are listed in the CHANGELOG ("Before upgrading" and "Removed") for apps that restyled them with::ng-deep.Deviations from
table-loading.mdmatNoDataRowrows, neither option (a) (a measured grid after the table) nor (b) (fake rows in the data source). Skeleton cells of sticky columns do not stick while scrolling sideways.tbody, not pertr(same sticky-safe result, one transition, no double dimming of nested tables);cursor: progressshows over the body only, so the header does not look busy.sd-button [loading]: an 18px spinner without a track,color="primary"while loading (sd-button forces the spinner colour percolor), and noaria-disabled(sd-button has no input for it).Tests
table.component.loading.spec.tscovers section 13 of the spec (items 1–18) andtable-loading.util.spec.tscovers merging, timers and skeleton shapes. The overlay test intable.component.spec.tsnow checks the status region.--code-coverage: 8360/8360 (seed 159142) after the review fixes. Table specs on each commit: 846 / 858 / 862 / 865 / 868 / 880 / 884. Full suite after commit 8: 8364/8364 (seed 978709).npm run lint,npm run test:theme,npm run check:scss-hex,npm run check:sync,npm run test:scripts, showcase unit tests (331/331).The showcase "Loading / empty / error" example gains a fake-delay switch (0 / 150 / 600 / 3000 ms), a "Lần tới lỗi" toggle and sorting, to try these by hand.
Screenshots
More checks: dark · forced colors · graphite · fill mode, first load · fill mode, refresh · sticky column scrolled during refresh · custom error template
The "before" shots use a library built from
main; the demos' fake delays were stretched for the shots only (the demo code is unchanged except the "Loading / empty / error" controls).🤖 Generated with Claude Code