wikipedia-pages-vue — Vue 3 port of wikipedia-pages-js
A Vite + Vue 3 (Composition API) application that is feature-identical to
wikipedia-pages-js: Wikipedia pages rendered in
vanilla-grid inside a vanilla-resize-box, with opaque continue-token
pagination against the public MediaWiki Action API.
It exists to demonstrate that the zero-dependency vanilla-grid /
vanilla-resize-box web components drop cleanly into a framework app — they
are plain custom elements, so Vue treats them as native DOM and only needs
thin wrapper components to bridge object props and CustomEvents.
What it shows
- Custom
DataManager—WikipediaDataManager(insamples/wikipedia-pages-vue/src/composables/useWikiDataManager.ts) extendswindow.DataManager, carried over verbatim from wikipedia-pages-js. Opaquecontinue-token pagination; the grid's(skip, pageSize)hint is ignored. - Thin custom-element wrappers —
samples/wikipedia-pages-vue/src/components/VnGrid.vueforwards attributes, sets the object-valueddataManageras a DOM property, bridgesvn-grid-*events to Vue emits, and exposes the imperative grid API viadefineExpose.samples/wikipedia-pages-vue/src/components/VnResizeBox.vueis purely declarative. - Reactive composables —
useI18n,useTheme,usePagesGrid,useWikiDataManagerport the wikipedia-pages-js modules to Vue reactivity. <vn-grid-toolbar>status bar via a Vue wrapper —samples/wikipedia-pages-vue/src/components/VnGridToolbar.vuewraps thesrc/vanilla-grid-toolbar/custom element. The state templates are injected viav-htmlbecause the toolbar clones the.contentfragment of its<template data-vn-grid-toolbar="…">children — Vue-rendered children of a<template>land inchildNodes, not.content, so slot-authored templates would clone as empty. The composable's reactivestatusMessage/statusTypeare bridged ontotoolbar.setStatusMessage()with awatch(generic "rows" wording, with the live selection count appended —… · 2 selected). TheexportToExcelcommand is overridden (registerCommand) for the app's file naming (theme-matched Excel styling is automatic — the grid'sexportToExcel()defaults tothemeStyle: 'auto') and returns the export's promise, so the toolbar handles its outcome (a cancelled export is not an error), and the customviewSelectioncommand (selected template, single selection only) opens the shared page-detail panel via the wrapper'scommandemit.- Multiple-row selection, row double-click → detail side panel, Excel export (theme-styled), 8 themes, EN/IT i18n, switchable category, resizable grid container — kept identical to wikipedia-pages-js.
- TypeScript throughout — every
.js/.vuesource file is TS (<script setup lang="ts">for SFCs). The only ambient-typing wrinkle:vanilla-grid,vanilla-resize-box, andvanilla-grid-toolbarship hand-authored.d.tsfiles but aren't npm packages, sosamples/wikipedia-pages-vue/src/types/global.d.tspulls them in via triple-slash references, and files that name their exported types directly (e.g.VanillaGridElement) import them from the sibling.d.tspath. Type-check the whole app withnpm run typecheck.
How vanilla-grid is loaded
The components ship as plain-JS globals that resolve their own feature scripts
and CSS relative to their <script> URL, so they are not imported as ES
modules. Instead:
samples/wikipedia-pages-vue/index.htmlloads them with classic script tags from/vendor/vanilla-resize-box/…,/vendor/vanilla-grid/…and/vendor/vanilla-grid-toolbar/….- A small Vite plugin in
samples/wikipedia-pages-vue/vite.config.tsserves the component directories (which live above this project root) under/vendor/…in dev (middleware) and copies them intodist/vendor/…for production. samples/wikipedia-pages-vue/src/main.tsawaitswindow.VanillaGridReadybefore mounting, so the grid element is defined andwindow.DataManagerexists.vite.config.tsalso marks everyvn-*tag (grid, columns, resize-box, toolbar and its sub-elements) asisCustomElementso the Vue compiler does not warn about them.
Resizing the grid
The grid sits in a src/vanilla-resize-box/ (aria-label="Resizable Wikipedia pages grid", passed straight through the VnResizeBox.vue wrapper). Drag the corner handle to resize it — resizing is pointer-driven, with no keyboard path.
The handle carries role="separator" and takes its accessible name from the host's aria-label — here "Resizable Wikipedia pages grid — resize handle" — but is not focusable, so it adds no tab stop to the page. This app's markup needs nothing beyond the aria-label it already carries.
Differences from wikipedia-pages-js
- No page reload on theme switch, no persistence. wikipedia-pages-vue swaps the app theme
<link href>reactively and re-themes the grid via its reactive:themebinding (the element re-loads its theme CSS on thethemeattribute change) — matching wikipedia-pages-js, which also switches themes live with no reload or persistence.App.vue'sonThemeChange()also callsPagesTab's exposedrefresh()(vianextTick, once the:themebinding has reached the element) to rebind the grid's visible cells and reset scroll to the top, without re-fetching from the DataManager. Every page load starts from the code-configured default theme (fluent), regardless of what was picked in a prior visit. Locale changes still reload and persist (the simplest way to re-init the grid's localized formatting), matching wikipedia-pages-js. - Storage keys are namespaced
wikipedia-pages-vue:(wikipedia-pages-vue:selectedLocale,wikipedia-pages-vue:selectedCategory).
The Wikipedia endpoint, pagination, sorting, and formatversion=2 rationale
are identical to wikipedia-pages-js — see that README for
the full API discussion.
Run
cd wikipedia-pages-vue
npm install
npm run dev # Vite dev server (default http://localhost:5173)
Build
npm run build # → wikipedia-pages-vue/dist/ (self-contained, incl. dist/vendor/)
npm run preview # serve the production build locally
npm run typecheck # vue-tsc (app + .vue) + tsc (vite.config.ts) — no emit
Project layout
wikipedia-pages-vue/
├── index.html # Vite entry — loads base+theme CSS, vendor scripts, mounts #app
├── vite.config.ts # isCustomElement + /vendor static-serving plugin
├── tsconfig.json # app config (src/**/*.ts, *.vue) — read by vue-tsc/editors by default
├── tsconfig.node.json # Node-context config for vite.config.ts (separate lib/types)
├── public/
│ ├── styles.css # base app stylesheet (linked before the theme — cascade order);
│ │ # sets overscroll-behavior-y: none (host-owed half of the grid's
│ │ # mobile touch contract — docs/vanilla-grid/00-index.md)
│ └── themes/app-*.css # app theme stylesheets (runtime <link> swap)
├── src/
│ ├── main.ts # awaits VanillaGridReady, mounts App
│ ├── App.vue # header, selectors, tab, detail panel, overlay
│ ├── types/
│ │ ├── global.d.ts # triple-slash refs pulling in the 3 sibling component .d.ts files
│ │ └── wiki.ts # shared WikiPage row interface (data manager ↔ grid ↔ detail panel)
│ ├── components/
│ │ ├── VnGrid.vue # <vn-grid> wrapper (events + dataManager + API)
│ │ ├── VnResizeBox.vue # <vn-resize-box> wrapper
│ │ ├── VnGridToolbar.vue # <vn-grid-toolbar> wrapper (v-html templates + API)
│ │ ├── PagesTab.vue # toolbar + grid + columns + usePagesGrid wiring
│ │ ├── PageDetailPanel.vue # detail side drawer
│ │ └── ThemeSwitchOverlay.vue # loading overlay
│ └── composables/
│ ├── useI18n.ts # reactive i18n (port of modules/i18n.js)
│ ├── useTheme.ts # theme switching (port of modules/theme.js)
│ ├── useWikiDataManager.ts # WikipediaDataManager class + factory
│ └── usePagesGrid.ts # grid lifecycle (port of pages-tab.js)