跳到正文
cccskills
免费在 GitHub 公开

handsontable-css-dev

Guides changes to Handsontable themes and CSS, including cell spacing, text, icons, and theme tokens—named styling settings. It explains compatibility rules and the files and checks needed to keep theme changes consistent.

中文简介正在准备中。当前显示英语简介。

原文查看原文说明

Use when working with Handsontable themes, CSS custom properties, SCSS files, theme tokens, or visual styling - covers theme architecture, CSS variable API, the strict CSS/JS separation rule, and the four-layer process for adding or renaming theme tokens

查看安装方法

适用场景

  • Adjust cell spacing without disrupting scrolling
  • Add or rename theme tokens
  • Fix text styles affected by the host page
  • Update icons across themes

包含的文件(1)

  • SKILL.md10.9 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

Theme and CSS Development

Themes

Three themes, each with a -no-icons variant (6 bundles): ht-theme-main (default), ht-theme-classic, ht-theme-horizon, and ht-theme-main-no-icons, ht-theme-classic-no-icons, ht-theme-horizon-no-icons. Test all 3 themes after any styling change.

Rules

  • CSS custom properties are the public API for theming. Renaming or removing one is a breaking change: keep the old variable working.
  • Renaming or removing a CSS class is a breaking change: keep the old class in the DOM.
  • CSS and JS live in separate files.
  • Every CSS feature must work in the browser-targets.js browsers (eslint-plugin-compat enforces it).

Cell padding comes from the variables

StylesHandler#getDefaultRowHeight() computes --ht-line-height + 2 * --ht-cell-vertical-padding + border-bottom-width, and Walkontable sizes a table's scroll range from the summed row heights. A padding written onto a td leaves the variable at the theme value, so the scroll range comes out wrong. Override --ht-cell-vertical-padding / --ht-cell-horizontal-padding and derive the td padding from them.

A nested grid built inside a hidden container masks this: its styles cache is empty, the derived row height reads null, and the engine measures the DOM.

File structure and commands

PathContents
src/styles/Base SCSS
src/themes/Theme SCSS
handsontable/styles/Compiled CSS, gitignored (handsontable/.gitignore), never committed; regenerated by build:styles/build:themes-css

build:styles compiles SCSS, build:themes-* builds theme assets, npm run stylelint --prefix handsontable lints CSS/SCSS.

No :has() in stylesheets

The stylelint rule handsontable/no-has-selector (error) bans :has() in src/**/*.{css,scss}: in Chrome it re-runs style invalidation across the host page on every grid DOM mutation, and the grid mutates on every scroll render. Drive the style from a class that JS toggles on the target element (reference pattern: SelectionManager #markActiveHeaderNeighbors and the -seam taggers).

The rule lives in .config/plugin/stylelint/, a pnpm file: dependency that is copied, not symlinked: run pnpm install after editing it.

An exception for state not re-evaluated during a scroll (dialog focus, dropdown selection, offscreen .htGhostTable) uses // stylelint-disable-next-line handsontable/no-has-selector -- <reason> with the reason why it is off the scroll path.

Text-bearing spans declare their own text metrics

Every <span> (or other inline element) the grid renders with visible text declares font-size, line-height, font-weight, letter-spacing, and font-family itself. A host rule on the bare element (span { font-size: 20px }, specificity (0,0,1)) beats an inherited value (#11306, DEV-75). The older guards cover two properties only: span.colHeader/span.rowHeader (tokens in _base.scss), .htCheckboxRendererLabel, .ht-sheets-bar__tab-label; their font-weight, letter-spacing, and font-family still leak.

Pattern: the component root (.ht-pagination, td, .ht-sheets-bar) carries the token (font-size: var(--ht-font-size)), and every text-bearing span below it declares the five properties as inherit. That wins over the host's element selector and follows a user's override on the root, provided the override beats the root's rule on specificity. Source order does not decide it, because the core stylesheet is injected into <head> at init and often loads after the user's CSS. Core's cell rule .handsontable :where(...) > td is (0,1,1): a user's .handsontable td ties and loses on order, .handsontable tbody td (0,1,2) wins. The bar's .handsontable.ht-pagination is (0,2,0): div.handsontable.ht-pagination (0,2,1) wins. State three limits in the PR:

  • A user rule on the guarded class at lower specificity than the guard now loses: .ht-multi-select-chip { font-size: 16px } (guard (0,2,0)), .ht-page-navigation-section__label { font-weight: 600 }, and .handsontable .ht-page-navigation-section__label (guard .handsontable.ht-pagination ... (0,3,0)).
  • A bare .ht-pagination { font-size } never beat the bar's own .handsontable.ht-pagination rule.
  • Offsets computed from the --ht-line-height token (multiselect and autocomplete arrow top, chip end padding) do not follow a user's td line-height override.

Scope the declarations to the guarded classes. Do not add a blanket .handsontable span { ... } rule: its specificity (0,1,1) overrides a user's .my-class on spans inside custom renderers.

tests/e2e/host-span-styles.spec.ts (fixture tests/fixtures/demo/host-span-styles.html) hosts a hostile span {} rule and asserts the guarded spans against their cascade parent. Its user-override tests prepend their rule to <head> (HostSpanStylesPage.prependUserStyles). Add every new text-bearing span to that spec. The fixture loads the compiled handsontable/styles/*.min.css and a dist/ bundle that injects the core stylesheet again at init, so after an SCSS edit run the full npm --prefix handsontable run build before trusting a spec run; build:styles and build:styles.min alone leave the bundle's copy stale and a reverted guard still passes.

Icons

Every built-in icon is a real element: <i class="ht-icon ht-icon-<kebab-name>" aria-hidden="true"></i>, built by two functions in handsontable/src/themes/engine/icons.ts:

  • createIcon(hot, name, options) builds a fresh icon (options: { flipInRtl?, className? }).
  • syncIcon(hot, container, slotClass, name, options) keeps exactly one icon in a slot a hook re-renders: creates it when missing, keeps it when the name is unchanged, replaces it when the name changed, and removes it when name is null. Use it wherever a header/draw hook decides per render whether an icon shows; do not hand-roll createIcon plus manual remove/replace bookkeeping there.

The glyph comes from the per-theme --ht-icon-<name> variable plus a generated .ht-icon-<name> rule (mask-image: var(--ht-icon-<name>); background-color: currentColor). .ht-icon (handsontable/src/styles/components/core/_icon.scss) carries no paint, no background-color and no mask-image: a -no-icons bundle ships neither the variable nor the .ht-icon-<name> rule, so mask-image resolves to none and the icon renders as nothing. Paint on the base class would render every icon in a -no-icons bundle as a solid square. Add paint only to a generated .ht-icon-<name> rule.

  • Glyph vs. chrome. A component with two pseudo-elements has one drawing the glyph and one drawing a background, border, ring, or hover surface. Only the glyph one is replaced by the <i> element (its @include mixins.pseudo / content: '' dropped); the chrome one keeps its content unchanged. Read both &::before/&::after rules before touching either (::before is not always the glyph). Mixing them up shipped four times: radio focus ring, sheets-bar chevron hover, checkbox box, collapsible-column indicator background.
  • RTL mirroring. A mirrored icon carries ht-icon--flip-rtl ([dir="rtl"] .ht-icon--flip-rtl { transform: scaleX(-1); }). Its positioning rule must never also set transform (such as translateY(-50%)): the declarations collide and the mirror silently stops, visible only under [dir="rtl"]. Center it with a negative margin or a flex/grid container.
  • Migrating a site: move the state rules too. Re-key every :hover, :focus, :disabled, :checked, or .is-active rule that colored the old pseudo-element onto .ht-icon (or a class the <i> carries); otherwise the glyph stays visually dead on state changes.
  • Adding or renaming an icon touches four lists that a test or tsc cross-checks: the generator side in handsontable/scripts/themes/figma/.ai/CONVENTIONS.md ("Adding or changing an icon") and IconKey/ICON_NAMES/VALID_ICON_KEYS at runtime.

Adding a new theme token: four layers

For a --ht-<component>-<property> variable and its JS token. A rename or removal follows the same playbook plus a legacy-alias path. Group the new entry next to related ones in each file.

Layer 1: static CSS defaults (6 files)

handsontable/src/themes/static/css/theme/ht-theme-{main,classic,horizon}{,-no-icons}.css

Define --ht-<kebab-case-name> and its resolved default per theme, usually via var(...) of lower-level tokens so overrides keep cascading. All themes declare the same key set. Symptom when missing: the variable is undefined in DevTools while ThemeBuilder tests that skip rendered styles pass.

Layer 2: token JS runtime defaults (3 files)

handsontable/src/themes/static/variables/tokens/{main,classic,horizon}.ts

These drive ThemeBuilder. camelCase keys mirror the CSS variable (paginationButtonBorderColor for --ht-pagination-button-border-color); values use 'tokens.otherTokenName' strings or primitive arrays like ['colors.palette.100', 'colors.palette.700']. Symptom when missing: createTheme() ignores the token.

Layer 3: validation allow-list

handsontable/src/themes/engine/utils/validation.ts: add the key to the VALID_TOKEN_KEYS Set, in the matching semantic section (e.g. // Pagination). Symptom when missing: ThemeBuilder logs [ThemeBuilder] Unknown token key: "xxx", and src/themes/engine/__tests__/builder.unit.js fails "should not warn for unknown token keys when using built-in tokens in createTheme" (it iterates every mainTokens key to catch layer 2/3 drift).

Layer 4: TypeScript types

handsontable/src/themes/types.ts: add the key to the TokenKey union. Edit the source; tmp/themes/types.d.ts is generated by build:types. Symptom when missing: npm run test:types --prefix handsontable (tsc -p ./test/types) fails with a TokenKey assignability error.

Layer 5 (soft): docs

docs/content/guides/styling/theme-customization/theme-customization.md: add the token to the variables reference table (CSS name + JS name, then description).

Naming

JS camelCase maps to CSS kebab-case with the --ht- prefix by convention (no generator): dialogContentPaddingHorizontal is --ht-dialog-content-padding-horizontal.

SCSS consumption

Reference the variable from the component SCSS under src/styles/components/ (e.g. _pagination.scss) as var(--ht-<name>). Use the var(--ht-<name>, <fallback>) form only for a legacy compatibility fallback, because a fallback masks missing-variable bugs.

Pre-flight

  1. CSS in all 6 theme files.
  2. JS token in all 3 runtime files.
  3. Key in the validation.ts allow-list.
  4. Key in TokenKey.
  5. npm run test:unit --prefix handsontable --testPathPattern=themes passes.
  6. npm run test:types --prefix handsontable passes.
  7. Docs table updated.

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

Develop and modify the Handsontable Angular wrapper with guidance on component lifecycle, settings updates, and NgZone, which controls change detection. It also covers standalone examples, global configuration, and the build and test workflow.

中文简介正在准备中。当前显示英语简介。

  • Fix settings updates in the Angular wrapper
  • Review change detection around grid operations
  • Update examples to standalone components
handsontable/handsontable2.2万2026年10月19日 更新

Open local or public web pages in BrowserStack Live for checks on real mobile devices or desktop browsers. The skill prepares temporary access to a local server, launches the requested session, and shares its dashboard link.

中文简介正在准备中。当前显示英语简介。

  • Check a local page on an Android device
  • Open a page in Safari on an iPhone or iPad
  • Inspect behavior in a specific desktop browser
handsontable/handsontable2.2万2026年10月19日 更新

Prepare user-facing changelog entries for source code changes. The skill determines whether an entry is needed, classifies the change, writes a clear title, and uses the project CLI to create a JSON file tied to the pull request.

中文简介正在准备中。当前显示英语简介。

  • Add a changelog entry for a bug fix
  • Classify a new feature or behavior change
  • Explain a breaking change and its replacement
handsontable/handsontable2.2万2026年10月19日 更新

Explore relationships between functions and files using a prebuilt code graph, a map of code dependencies. Trace bugs, inspect callers and execution paths, and assess the impact of refactoring or proposed changes.

中文简介正在准备中。当前显示英语简介。

  • Explore callers and dependencies in unfamiliar code
  • Trace a bug across multiple files
  • Assess the impact of renaming or removing code
handsontable/handsontable2.2万2026年10月19日 更新

Work with Handsontable row and column positions by distinguishing source data, displayed order, and rendered cells. It guides index conversion and helps investigate misplaced data when sorting, filtering, hiding, or moving rows and columns.

中文简介正在准备中。当前显示英语简介。

  • Investigating misplaced rows or columns
  • Converting between source and displayed indexes
  • Combining filters with manual column movement
handsontable/handsontable2.2万2026年10月19日 更新

Create Handsontable documentation examples for JavaScript, TypeScript, React, Angular, and Vue. The skill explains source files, generated JavaScript, required imports and license settings, embedding examples in guides, and checking that each framework version displays correctly.

中文简介正在准备中。当前显示英语简介。

  • Adding a code example to a feature guide
  • Preparing examples for multiple frameworks
  • Generating JavaScript examples from TypeScript
handsontable/handsontable2.2万2026年10月19日 更新

查看 handsontable 的全部技能

报告此技能的问题