Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
21d243c
feat(docs): namespace docs examples by version
demtario Jul 17, 2026
f5ebd88
Merge pull request #59 from handsontable/feature/DEV-2100_bucket-foun…
demtario Jul 17, 2026
48504ec
feat(docs): automate versioned example imports
demtario Jul 17, 2026
266157b
Merge pull request #62 from handsontable/feature/DEV-2101_docs-exampl…
demtario Jul 17, 2026
da85cf2
fix(runtime): sync CDN CSS version
demtario Jul 17, 2026
2a515e1
Merge pull request #64 from handsontable/feature/DEV-2103_rewrite-cdn…
demtario Jul 17, 2026
e054f00
chore(docs): update versioned docs examples
demtario Jul 17, 2026
94c0bd6
Merge pull request #65 from handsontable/chore/docs-example-buckets
demtario Jul 17, 2026
3277a52
feat(docs): load versioned example buckets
demtario Jul 17, 2026
22a1bbe
feat(runner): add starter compatibility matrix e2e + version.ts floor…
demtario Jul 17, 2026
2f2de99
chore(runner): gitignore starter-matrix run reports
demtario Jul 20, 2026
6f4c133
Merge pull request #67 from handsontable/feature/DEV-2102_starter-com…
demtario Jul 20, 2026
45ceef9
Merge pull request #66 from handsontable/feature/DEV-2104_bucket-awar…
demtario Jul 20, 2026
c8664c2
chore(docs): remove legacy docs snapshot
demtario Jul 17, 2026
5a67c45
Merge pull request #60 from handsontable/chore/DEV-2100-remove-legacy…
demtario Jul 20, 2026
91c7371
fix(astro): disable dev toolbar to stop Tier-2 preview 504s
demtario Jul 20, 2026
36620d1
Merge pull request #68 from handsontable/fix/DEV-2109_astro-tier2-504…
demtario Jul 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
177 changes: 177 additions & 0 deletions .github/workflows/import-docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
name: Import versioned docs examples

on:
workflow_dispatch: {}
repository_dispatch:
types: [docs-examples-sync]

permissions:
contents: read

concurrency:
group: import-docs-examples
cancel-in-progress: false

jobs:
prepare:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.matrix.outputs.matrix }}
steps:
- id: matrix
name: Build docs branch matrix
env:
EVENT_NAME: ${{ github.event_name }}
DISPATCH_DOCS_BRANCH: ${{ github.event.client_payload.docs_branch }}
run: |
node --input-type=module <<'NODE'
import fs from "node:fs";

const releaseBranch = (version) => {
const match = typeof version === "string" && version.match(/^(\d+)\.(\d+)\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/);
if (!match) throw new Error(`npm dist-tags.latest must be a concrete semver version; got ${JSON.stringify(version)}`);
return { docs_branch: `prod-docs/${match[1]}.${match[2]}`, bucket: `${match[1]}.${match[2]}` };
};

const dispatchBranch = process.env.DISPATCH_DOCS_BRANCH;
let matrix;
if (process.env.EVENT_NAME === "repository_dispatch" && dispatchBranch) {
if (dispatchBranch === "develop") {
matrix = [{ docs_branch: "develop", bucket: "next" }];
} else {
const match = dispatchBranch.match(/^prod-docs\/(\d+)\.(\d+)$/);
if (!match) throw new Error(`Unsupported repository_dispatch docs_branch: ${JSON.stringify(dispatchBranch)}`);
matrix = [{ docs_branch: dispatchBranch, bucket: `${match[1]}.${match[2]}` }];
}
} else {
const response = await fetch("https://registry.npmjs.org/handsontable");
if (!response.ok) throw new Error(`Could not fetch npm dist-tags.latest: registry returned ${response.status}`);
const registry = await response.json();
matrix = [releaseBranch(registry?.["dist-tags"]?.latest), { docs_branch: "develop", bucket: "next" }];
}

fs.appendFileSync(process.env.GITHUB_OUTPUT, `matrix=${JSON.stringify(matrix)}\n`);
NODE

import:
needs: prepare
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include: ${{ fromJSON(needs.prepare.outputs.matrix) }}
defaults:
run:
working-directory: runner
steps:
- uses: actions/checkout@v4

- name: Check out Handsontable docs source
uses: actions/checkout@v4
with:
repository: handsontable/handsontable
ref: ${{ matrix.docs_branch }}
path: handsontable
token: ${{ secrets.DOCS_REPO_TOKEN || github.token }}
sparse-checkout: |
/docs/content/**
/handsontable/package.json
sparse-checkout-cone-mode: false

- id: import
name: Import ${{ matrix.bucket }} bucket
continue-on-error: true
run: |
set -o pipefail
node pipeline/import-docs.mjs \
--docs="$GITHUB_WORKSPACE/handsontable/docs" \
--docs-branch="${{ matrix.docs_branch }}" 2>&1 | tee import-docs.log

- name: Summarize ${{ matrix.bucket }} import
if: always()
working-directory: .
env:
BUCKET: ${{ matrix.bucket }}
DOCS_BRANCH: ${{ matrix.docs_branch }}
IMPORT_OUTCOME: ${{ steps.import.outcome }}
run: |
node --input-type=module <<'NODE'
import fs from "node:fs";
import path from "node:path";

const manifestPath = path.join(
"runner",
"apps",
"authoring",
"public",
"docs-examples",
process.env.BUCKET,
"manifest.json",
);
const lines = [
`## Docs examples: \`${process.env.BUCKET}\``,
"",
`- Docs branch: \`${process.env.DOCS_BRANCH}\``,
`- Import result: **${process.env.IMPORT_OUTCOME}**`,
];
if (fs.existsSync(manifestPath)) {
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
const counts = manifest.examples.reduce((result, { framework }) => {
result[framework] = (result[framework] || 0) + 1;
return result;
}, {});
lines.push(`- Handsontable version: \`${manifest.hotVersion}\``);
lines.push(`- Examples: ${manifest.count}`);
lines.push(`- By framework: ${Object.entries(counts).map(([name, count]) => `${name} (${count})`).join(", ")}`);
} else {
lines.push("- Manifest: not generated");
}
fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, `${lines.join("\n")}\n\n`);
NODE

- name: Fail when import failed
if: steps.import.outcome == 'failure'
run: exit 1

- name: Stage ${{ matrix.bucket }} bucket
run: |
mkdir -p "$RUNNER_TEMP/docs-examples"
cp -R "apps/authoring/public/docs-examples/${{ matrix.bucket }}" "$RUNNER_TEMP/docs-examples/${{ matrix.bucket }}"

- name: Upload ${{ matrix.bucket }} bucket
uses: actions/upload-artifact@v4
with:
name: docs-examples-${{ matrix.bucket }}
path: ${{ runner.temp }}/docs-examples
retention-days: 7

publish:
needs: [prepare, import]
if: needs.import.result == 'success'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4

- name: Download generated buckets
uses: actions/download-artifact@v4
with:
pattern: docs-examples-*
merge-multiple: true
path: runner/apps/authoring/public/docs-examples

- name: Create or update generated-content pull request
uses: peter-evans/create-pull-request@v7
with:
branch: chore/docs-example-buckets
base: ${{ github.ref_name }}
commit-message: "chore(docs): update versioned docs examples"
title: "chore(docs): update versioned docs examples"
body: |
Generated by the **Import versioned docs examples** workflow.

The workflow imported the requested Handsontable documentation bucket(s)
with concrete npm dependency versions.
labels: automated
8 changes: 7 additions & 1 deletion examples/astro/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@ import { defineConfig } from 'astro/config';

// https://astro.build/config
export default defineConfig({
devToolbar: {
// The toolbar's own client entrypoint (@id/astro/runtime/client/dev-toolbar/entrypoint.js)
// intermittently 504s through the Tier-2 preview proxy; the toolbar has no use in an
// embedded preview iframe anyway.
enabled: false,
},
vite: {
server: {
allowedHosts: true, // allow CodeSandbox preview hosts (*.csb.app)
allowedHosts: true, // allow Handsontable Tier-2 preview hosts (*.demos.handsontable.com)
},
},
});
3 changes: 3 additions & 0 deletions runner/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ build/
test-results/
playwright-report/
.playwright/

# generated starter-matrix run reports — paste into the ticket/PR instead
docs/reports/

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"framework":"angular","displayName":"Accessories And Menus ▸ Context menu · Menu item configuration options · Angular","tier":2,"engine":"container","sandpackTemplate":null,"sandpackEnvironment":null,"container":"angular","htWrappers":["@handsontable/angular-wrapper"],"entry":"/src/main.ts","htmlEntry":"/src/index.html","devCommand":"pnpm exec ng serve --host 0.0.0.0 --disable-host-check --port 3000","buildCommand":"ng build","outputDir":"dist","outputGlob":"dist/*/browser","staticExport":false,"spaMode":false,"port":3000,"installCommand":"pnpm install","htCoreRange":"18.0.0","fileCount":7,"assets":[],"skipped":[],"files":{"/package.json":"{\n \"name\": \"handsontable-angular-example\",\n \"version\": \"1.0.0\",\n \"private\": true,\n \"packageManager\": \"pnpm@10.34.5\",\n \"scripts\": {\n \"ng\": \"ng\",\n \"start\": \"ng serve\",\n \"build\": \"ng build\"\n },\n \"dependencies\": {\n \"handsontable\": \"18.0.0\",\n \"@handsontable/angular-wrapper\": \"18.0.0\",\n \"@angular/animations\": \"21.x\",\n \"@angular/common\": \"21.x\",\n \"@angular/compiler\": \"21.x\",\n \"@angular/core\": \"21.x\",\n \"@angular/forms\": \"21.x\",\n \"@angular/platform-browser\": \"21.x\",\n \"@angular/platform-browser-dynamic\": \"21.x\",\n \"@angular/router\": \"21.x\",\n \"rxjs\": \"~7.8.0\",\n \"tslib\": \"^2.3.0\",\n \"zone.js\": \"~0.15.0\",\n \"@angular-devkit/build-angular\": \"21.x\",\n \"@angular/cli\": \"21.x\",\n \"@angular/compiler-cli\": \"21.x\",\n \"typescript\": \"~5.9.0\"\n },\n \"devDependencies\": {}\n}","/angular.json":"{\n \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n \"version\": 1,\n \"newProjectRoot\": \"projects\",\n \"projects\": {\n \"app\": {\n \"projectType\": \"application\",\n \"root\": \"\",\n \"sourceRoot\": \"src\",\n \"prefix\": \"app\",\n \"architect\": {\n \"build\": {\n \"builder\": \"@angular-devkit/build-angular:application\",\n \"options\": {\n \"outputPath\": \"dist/app\",\n \"index\": \"src/index.html\",\n \"browser\": \"src/main.ts\",\n \"polyfills\": [\n \"zone.js\"\n ],\n \"tsConfig\": \"tsconfig.json\",\n \"assets\": [],\n \"styles\": [],\n \"scripts\": []\n },\n \"configurations\": {\n \"development\": {\n \"optimization\": false,\n \"sourceMap\": true\n }\n },\n \"defaultConfiguration\": \"development\"\n },\n \"serve\": {\n \"builder\": \"@angular-devkit/build-angular:dev-server\",\n \"configurations\": {\n \"development\": {\n \"buildTarget\": \"app:build:development\"\n }\n },\n \"defaultConfiguration\": \"development\"\n }\n }\n }\n }\n}","/tsconfig.json":"{\n \"compilerOptions\": {\n \"outDir\": \"./dist/out-tsc\",\n \"strict\": true,\n \"noImplicitOverride\": true,\n \"noPropertyAccessFromIndexSignature\": true,\n \"noImplicitReturns\": true,\n \"noFallthroughCasesInSwitch\": true,\n \"skipLibCheck\": true,\n \"esModuleInterop\": true,\n \"sourceMap\": true,\n \"declaration\": false,\n \"experimentalDecorators\": true,\n \"moduleResolution\": \"bundler\",\n \"importHelpers\": true,\n \"target\": \"ES2022\",\n \"module\": \"ES2022\",\n \"useDefineForClassFields\": false,\n \"lib\": [\n \"ES2022\",\n \"dom\"\n ]\n },\n \"angularCompilerOptions\": {\n \"enableI18nLegacyMessageIdFormat\": false,\n \"strictInjectionParameters\": true,\n \"strictInputAccessModifiers\": true,\n \"strictTemplates\": true\n }\n}","/src/index.html":"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <title>Handsontable Angular Example</title>\n <base href=\"/\">\n <link rel=\"stylesheet\" href=\"https://unpkg.com/handsontable@18.0.0/dist/handsontable.full.min.css\" />\n <style>body { padding: 1rem; font-family: system-ui, -apple-system, sans-serif; }</style>\n</head>\n<body>\n <div>\n <app-example3></app-example3>\n </div>\n</body>\n</html>","/src/main.ts":"import { bootstrapApplication } from '@angular/platform-browser';\nimport { registerAllModules } from 'handsontable/registry';\nimport { AppComponent } from './app/app.component';\nimport { appConfig } from './app/app.config';\n\nregisterAllModules();\n\nbootstrapApplication(AppComponent, appConfig).catch(err => console.error(err));","/src/app/app.component.ts":"import { Component, OnInit } from '@angular/core';\nimport Handsontable from 'handsontable';\nimport { GridSettings, HotTableModule } from '@handsontable/angular-wrapper';\n\n@Component({\n selector: 'app-example3',\n standalone: true,\n imports: [HotTableModule],\n template: `\n <hot-table\n [settings]=\"hotSettings!\" [data]=\"hotData\">\n </hot-table>\n `,\n})\nexport class AppComponent implements OnInit {\n\n readonly hotData = [\n ['', 'Tesla', 'Nissan', 'Toyota', 'Honda', 'Mazda', 'Ford'],\n ['2017', 10, 11, 12, 13, 15, 16],\n ['2018', 10, 11, 12, 13, 15, 16],\n ['2019', 10, 11, 12, 13, 15, 16],\n ['2020', 10, 11, 12, 13, 15, 16],\n ['2021', 10, 11, 12, 13, 15, 16],\n ];\n\n hotSettings!: GridSettings;\n\n ngOnInit() {\n const contextMenuSettings = {\n callback(key: string, selection: any, clickEvent: any) {\n // Common callback for all options\n console.log(key, selection, clickEvent);\n },\n items: {\n row_above: {\n disabled(this: Handsontable): boolean {\n // `disabled` can be a boolean or a function\n // Disable option when first row was clicked\n return this.getSelectedLast()?.[0] === 0; // `this` === hot\n },\n },\n // A separator line can also be added like this:\n // 'sp1': '---------'\n // and the key has to be unique\n sp1: { name: '---------' },\n row_below: {\n name: 'Click to add row below',\n },\n about: {\n // Own custom option\n name() {\n // `name` can be a string or a function\n return '<b>Custom option</b>'; // Name can contain HTML\n },\n hidden(this: Handsontable): boolean {\n // `hidden` can be a boolean or a function\n // Hide the option when the first column was clicked\n return this.getSelectedLast()?.[1] == 0; // `this` === hot\n },\n callback() {\n // Callback for specific option\n setTimeout(() => {\n alert('Hello world!'); // Fire alert after menu close (with timeout)\n }, 0);\n },\n },\n colors: {\n // Own custom option\n name: 'Colors...',\n submenu: {\n // Custom option with submenu of items\n items: [\n {\n // Key must be in the form 'parent_key:child_key'\n key: 'colors:red',\n name: 'Red',\n callback() {\n setTimeout(() => {\n alert('You clicked red!');\n }, 0);\n },\n },\n { key: 'colors:green', name: 'Green' },\n { key: 'colors:blue', name: 'Blue' },\n ],\n },\n },\n credits: {\n // Own custom property\n // Custom rendered element in the context menu\n renderer() {\n const elem = document.createElement('marquee');\n\n elem.style.cssText = 'background: lightgray; color: #222222;';\n elem.textContent = 'Brought to you by...';\n\n return elem;\n },\n disableSelection: true,\n isCommand: false,\n },\n },\n };\n\n this.hotSettings = {\n rowHeaders: true,\n colHeaders: true,\n height: 'auto',\n contextMenu: contextMenuSettings,\n autoWrapRow: true,\n autoWrapCol: true,\n };\n }\n}","/src/app/app.config.ts":"import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';\nimport { registerAllModules } from 'handsontable/registry';\nimport { HOT_GLOBAL_CONFIG, HotGlobalConfig, NON_COMMERCIAL_LICENSE } from '@handsontable/angular-wrapper';\n\n// register Handsontable's modules\nregisterAllModules();\n\nexport const appConfig: ApplicationConfig = {\n providers: [\n provideZoneChangeDetection({ eventCoalescing: true }),\n {\n provide: HOT_GLOBAL_CONFIG,\n useValue: { license: NON_COMMERCIAL_LICENSE } as HotGlobalConfig,\n },\n ],\n};"},"docsPath":"guides/accessories-and-menus/context-menu/angular/example3.ts","breadcrumb":["Accessories And Menus","Context menu"],"guide":"guides/accessories-and-menus/context-menu/context-menu.md","guideTitle":"Context menu","exampleId":"example3","exampleTitle":"Menu item configuration options","docPermalink":"/context-menu","lang":"Angular"}
Loading
Loading