Repository navigation
feat(chip): give sd-chip the shared chip look and drop dead API (CH3) - #105
Merged
Merged
Conversation
Add chip.visual.spec.ts (21 cases, LayoutTestTheme with the global theme loaded). It measures the sm / md / lg scale: field 32/40/48, chip 20/24/28 centred, text 12/13/14, padding, the remove and lock icons, the clear button with its icon, the "+N" button and the 4px gap. It also checks that two chips stay on one 40px line, that md and lg fit three chips and the clear button on one line with a typing area of at least 80px, that a folded table cell stays 32px without a placeholder and unfolds on focus, the chip shape (radius, surface, 1px border, tabular numbers), the locked chip (class, background, lock icon with tooltip, accessible name) and the D1 cleanup (no onSelect, selectable, separatorKeysCodes or autocomplete; isFocused is a signal). Add three chip-calendar cases for the clear button size per size (24/16, 28/18, 32/20). Drop the onSelect autocomplete spec from chip.component.spec.ts and read isFocused as a signal. Add the chip-locked-bg and chip-more-hover-bg pairs to the theme contrast test. These specs fail until the code commit that follows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Implement design handoff CH3 (C1-C4, D1, D2):
- C1/C2: a sd-chip-row($block, ...) mixin in core/form.scss styles both
mat-form-field.sd-chip-field and .sd-chip-calendar-field. Field
32/40/48, chip 20/24/28 centred, text 12/13/14, radius 6px, surface
background, 1px --sd-border outline, tabular numbers, 4px gap,
remove / lock icons 12/14/16 and square suffix buttons 24/28/32 with
16/18/20 icons. size="lg" is now styled. The typing area keeps at
least 80px so md and lg fit three chips and the clear button on one
line, and a folded field (sd-chip-field--folded) lets the input
shrink and drops the placeholder so a table cell stays 32px.
- C3: a chip that removable refuses gets --sd-chip-locked-bg, a lock
icon with the "Không xoá được" tooltip and the name
"{value}, Không xoá được".
- C4: chip.component.scss has no ::ng-deep, !important or hex left.
c-md, .no-padding-wrapper and the 10px margin-top are gone.
- D1: remove onSelect, selectable, separatorKeysCodes, the unused
autocomplete and progress spinner, the effect that assigned #name and
SdRemovableChipPipe. isFocused is a signal, and the sdChanges
subscription uses takeUntilDestroyed instead of ngOnDestroy. The
label slot renders only with labelPlacement="top", like sd-date and
sd-select.
- D2: rewrite sd-chip.md.
Add the --sd-chip-locked-bg and --sd-chip-more-hover-bg tokens. The
chip-calendar locked and "+N" hover tokens keep their names and default
to the shared ones. chip-calendar drops its ::ng-deep field block for
the mixin, which also fixes its clear button icon (16px at every size
before, now 18/20 at md/lg). Update THEME.md and CHANGELOG [Unreleased]
(Changed (BREAKING for consumers) with a migration diff, Added,
Changed, Fixed). The showcase size section shows sm / md / lg with a
locked chip. Roll out with npm run sync.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5 tasks done
sdcorejs
added a commit
that referenced
this pull request
Oct 26, 2026
…sd-page-list (E1) (#106) * feat(badge): export SdBadgeData and use it for sd-table useBadge (PR 1) First step of the page-layouts handoff (doc 02). SdBadgeData describes one badge passed through an option instead of an <sd-badge> tag; sd-page [badges] will reuse it in the next step. - components/badge: new SdBadgeData { title, type, color, icon, label, tooltip }, exported from @sdcorejs/angular/components/badge. title keeps null so objects of the old internal Badge shape still type-check. - components/table: UseBadgeFunc / UseBadgeValuesFunc return SdBadgeData; SdTableDisplay.badge gains label and tooltip; the cell binds label and uses the badge tooltip only when the column has no tooltip. - Docs: SdBadgeData model section in sd-badge.md, badge shape in sd-table.md, CHANGELOG entry. - Rolled out to v20/v21/v22 with npm run sync. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(page): sd-page P0 — backLink, badges, width and new slots (PR 2) Second step of the page-layouts handoff (doc 01). sd-page - backLink (URL or router commands) renders a real <a routerLink> before the title; the title, description and badges still come from inputs. headerLeft still replaces the left area and then drops backLink with a dev-mode warning. - [badges]: SdBadgeData[] next to the title as md badges, round by default; badges without a title are skipped; more than two or a repeated type warn in dev mode. - width: full (default) | wide 1200 | medium 960 | narrow 720. The column limit includes 24px side padding; subheader and footer use the same padding. The host gets sd-page--wide/medium/narrow. - Slots: [sdSubheader] (in the header block, outside the scroll area), [sdAside] (320px sticky column, under the body below 1200px of viewport), [sdFooterLeft]/[sdFooterRight] (footer pinned to the bottom, hidden while empty), and [sdHeaderLeft]/[sdHeaderRight] aliases. - The title renders in <h1 class="T20M"> with its margin reset; the component is OnPush. - The new slots are projected without wrappers, so a page that uses none of them keeps its header and body DOM; only a hidden empty <footer> is added. Also - i18n: core.common.back and core.module.layout.page.actions in the five catalogs. - Theme: component tokens page-divider, page-back-fg, page-back-hover-bg. - Docs: new sd-page.md, sd-layout.md row, CHANGELOG (Added and Changed). - Showcase: components/page demo (4 sections) and registry entry. The registry spec demo total goes from 450 to 457: 4 new sections plus 3 chip sections added by #103-#105 without updating the count (showcase Karma does not run in CI). Route-shell test counts updated. - Rolled out to v20/v21/v22 with npm run sync. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(page): sd-page-detail and sd-page-form, toc and leave guard (PR 3) Third step of the page-layouts handoff (docs 04 and 05). sd-page - sd-page-detail and sd-page-form are selectors of SdPageComponent; the host tag picks the default width (sd-page full, sd-page-detail wide, sd-page-form medium). An explicit width still wins. - aside: 'end' (default) | 'start'. start puts the aside column first from 1200px of viewport; below that it stays under the body. - toc lists the titled top-level sd-sections of the body in the aside column as a <nav> (core.module.layout.page.toc), scrolls to a section on click and marks the section at the top of the scroll area with aria-current. The list is collected after render, because a nested section only reaches its parent's DOM when the parent view renders. Hidden below 1200px. sd-anchor is not used: it wraps its own items and owns its scroll area, so it cannot list sections of the page body. Leave guard - sdPageFormLeaveGuard (CanDeactivateFn) asks through SdUnsavedChangesService with the Core confirm dialog (SdConfirmService) and the doc 05 texts, by passing its own adapter for that prompt. SD_UNSAVED_CHANGES_CONFIRMATION_ADAPTER and sdUnsavedChangesGuard keep their behaviour. - SdUnsavedChangesConfirmOptions.adapter: per-call adapter. - registerSdUnsavedChangesForm(..., { dirty: 'value' }): dirty only while the value differs from the snapshot; default 'control' is unchanged. Also - i18n: core.module.layout.page.toc and leave.{title,message,stay,leave} in the five catalogs. - Docs: sd-page.md (selectors, aside, toc, leave guard), sd-unsaved-changes.md, sd-layout.md row, CHANGELOG. - Showcase: two more page demos (detail with the aside first, form with a table of contents); registry demo total 459. - Rolled out to v20/v21/v22 with npm run sync. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(page-list): sd-page-list with the E1 height chain and sd-table fill mode (PR 4) Fourth step of the page-layouts handoff (doc 03, acceptance E1 of doc 10). sd-page-list (modules/layout) - Wraps an sd-page, forwards headerRight, sdSubheader and the footer slots, and renders the router outlet for the detail child route. - Height chain: the body is a 16px padded flex column (gap 12px) and the only scroll area; the main table (the last direct sd-table, or an [sdPageListMain] wrapper, chosen again by a MutationObserver) takes the space left. - compact() = autoCompact (default true) and a viewport lower than 720px. The main table's external filter then starts collapsed; content reads compact() from SD_PAGE_LIST to fold its own summary. - detail="page" hides the list (display: none) while a child route is open, then restores every scroll position it captured and the focus of the element that opened the child. detail="drawer" keeps the list. - SD_PAGE_LIST gives the content compact, childActive and detail. sd-table - SD_TABLE_LAYOUT (new, in the table entry so the table does not depend on modules/layout) names a fill target. The table in it gets sd-table--fill: flex 1 1 0, a measured min-height (fixed parts + header + 5 rows, kept in sync by Mutation/ResizeObserver), a data area of at least header + 5 rows and a footer of at least 48px. - filter.externalFilterCollapsed sets the initial collapsed state; the user's choice holds until the default changes. Not applied when collapsible is false. - One-line rows are 36px (--mat-table-row-item-container-height), instead of Material's 52px, as agreed for every sd-table. Showcase - /demo/page-list: full-viewport live list (128 seeded orders) with ?cfg= toggles for tabs, score card, quick search, external filter, selection, record count, page size, read state, autoCompact and detail; child route :id/detail as a page or a 600px drawer. - e2e/page-list.e2e.ts: E1-1..E1-6 at 1440x960, 1440x800, 1280x768 and 1440x650 (runs in the asia-bangkok project only; layout does not depend on the zone). E1-4 measures the data area because the external filter lives inside sd-table. - components/page-list docs page (2 demos); registry and route-shell counts updated. Docs: sd-page-list.md, sd-table.md (row height, fill mode, externalFilterCollapsed), sd-layout.md, CHANGELOG (Added; Changed for the 36px rows). Rolled out to v20/v21/v22 with npm run sync. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(page-list): wait for page 2 rows before selecting in the E1-6 page test On the CI runner the paginator label switched to 21-40 on click while the rows were still those of page 1 until the delayed read landed, so the test checked ĐH-2400 of page 1 and found the first row of page 2 unchecked after Back. Wait for the first row of page 2 (ĐH-2380) before selecting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(page): render the sd-page header as <header>; run E1 on a bare sd-table screen Two items of the handoff left out of the first four commits: - doc 01 (a11y): the header of sd-page is a <header> element. Classes (c-page-header bg-white) and children are unchanged; the legacy DOM spec now checks the tag. sd-page.md and the CHANGELOG entry say so. - doc 10 (E1): also run E1-1/2/3 on a screen with only sd-table (no quick search, no filter row, no footer buttons) at the four viewports. v20-v22 are rolled out together with the merge of main that follows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: nghiatt15_onemount <nghiatt15@onemount.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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
CH3 of the sd-chip work in the design handoff
.sdcorejs/handoff/design-icon-chip-table/(spec inchip.md, items C1–C4, D1 and D2). It gives sd-chip the chip look of sd-chip-calendar through one shared SCSS mixin, marks locked chips, removes the dead autocomplete API and rewritessd-chip.md. CH1 (#103) and CH2 (#104) are already onmain, so this PR is based onmaindirectly.The work is two commits. The first adds the specs, which fail on their own. The second is the code,
npm run sync, docs, CHANGELOG and the showcase.What changed
sd-chip-row($block, …)mixin at the end ofcore/form.scssstylesmat-form-field.sd-chip-fieldand.sd-chip-calendar-field. Field 32 / 40 / 48px, chip 20 / 24 / 28px centred, text 12 / 13 / 14px, padding 8 / 8 / 10px, 4px between chips, square suffix buttons 24 / 28 / 32px with 16 / 18 / 20px icons.size="lg"now has a style. Themdfield is no longer taller than other controls.sd-chip-field--folded) the input shrinks and shows no placeholder, so asize="sm"table cell stays 32px.--sd-surfacebackground, 1px--sd-borderoutline, tabular digits, weight 400, transparent state layer.removablekeeps gets--sd-chip-locked-bg, alockicon in the place of ✕ with the tooltip “Không xoá được”, and the accessible name “{value}, Không xoá được”. Disabled fields show neither ✕ nor the lock.chip.component.scsshas no::ng-deep,!importantor hex left.c-md,.no-padding-wrapperand the 10pxmargin-topare gone.onSelect(),selectable,separatorKeysCodes, theMatAutocompleteModule/MatProgressSpinnerModuleimports, the effect that assigned#nameandSdRemovableChipPipe.isFocusedis a signal, and thesdChangessubscription usestakeUntilDestroyedinstead ofngOnDestroy. The label slot renders only withlabelPlacement="top", like sd-date and sd-select.sd-chip.mdrewritten: autoIds, the size scale, locked chips,maxVisible, the label slot, model, keyboard, the public API table, visual cues, E2E hooks, accessibility and anti-patterns.Also in this PR:
--sd-chip-locked-bg(55%--sd-surface-mutedon--sd-surface) and--sd-chip-more-hover-bg(6%--sd-texton--sd-surface-muted), light and dark.--sd-chip-calendar-chip-locked-bgand--sd-chip-calendar-more-hover-bgkeep their names and now default to the shared tokens.THEME.mdand the contrast test cover both.:host ::ng-deepfield block moved into the mixin, open state included. The only::ng-deepleft in its SCSS is the existing popup panel rule. This also fixes its clear button icon, which a shared.sd-clear-btn sd-iconrule forced to 16px at every size; it is now 18 / 20px at md / lg.clearable. The demo drops ahelperTextattribute sd-chip does not have.Changes for consumers (CHANGELOG
[Unreleased])onSelect(),selectableandseparatorKeysCodesare gone. The template never rendered an autocomplete, so only code that calledonSelect()itself is affected.SdRemovableChipPipe(sdRemovableChip) is no longer exported from@sdcorejs/angular/forms/chip.isFocusedis aSignal<boolean>, andSdChipno longer hasngAfterViewInit()/ngOnDestroy().--sd-chip-locked-bg,--sd-chip-more-hover-bg.API consistency review
Following the mandatory naming and public API consistency review in AGENTS.md.
onSelect,selectable,separatorKeysCodes,SdRemovableChipPipe,ngAfterViewInit/ngOnDestroy.isFocusedgoes frombooleantoSignal<boolean>. sd-chip-calendar still has a plainisFocusedfield, so the two differ here. Changing sd-chip-calendar is outside CH3.iconSizes: same name, but noopenentry, because sd-chip has no calendar button.lockedLabel: same name and key pattern (core.form.chip.locked, which CH2 added).chipLabel(item): same name. It returnsnullfor a removable chip, so the chip keeps its own text as its name, while sd-chip-calendar always returns the full date.sd-chip-field,sd-chip__chip,sd-chip__chip--locked,sd-chip__lock,sd-chip__suffix,sd-chip-field--folded.core.form.chip.*andcore.form.chip-calendar.*keys stay as they are.npm run sync.npm run check:syncis green on the committed tree.Deviations from
chip.mdcore/form.scss, not in each component's SCSS. From a component, reaching the inside of Material's chip and form field needs::ng-deep, which the handoff forbids, and@use-ingform.scssfrom a component would copy the global CSS.--sd-surface-muted.size="sm"table cell was 56px tall.labelPlacement="top"only, like sd-date and sd-select.Verification
All of this ran locally on exact Node 24.22.1:
npx ng test sdcorejs-angular --watch=false --browsers=ChromeHeadlessCI --code-coveragepasses 7947/7947. Statements 90.03%, Branches 79.35%, Functions 90.64%, Lines 92.46%.npm run lintnpx ng build sdcorejs-angularcheck:i18n,check:i18n-paritynpm run test:theme(53/53),test:theme-token-list(3/3),npm run check:scss-hex(0 findings),npm run test:showcase-examples(17/17)npm run sync+npm run check:syncchip.visual.spec.ts(with the global theme loaded throughLayoutTestTheme) and 3 sd-chip-calendar clear-button cases. Of the first 17 visual cases, 16 failed before the code. The 4 added later came from layout problems found on the showcase (the two-line md / lg field and the 56px folded cell).size="sm"table cell with[maxVisible]="2"is 32px: 2 chips, “+3”, no placeholder.Not verified: final screenshots (the preview pane does not paint while hidden), 1280px and 768px widths, dark mode in the UI (the tokens have dark values and pass the contrast test), forced-colors mode and a real screen reader. The sd-chip-calendar showcase page was not loaded; it is covered by the geometry specs. v20 and v21 run in CI.
🤖 Generated with Claude Code