@0k/vue-period-picker

Compact gettext-aware Vue 3 period picker with live durations and calendar ranges.
236
Install
npm install @0k/vue-period-picker
Latest Version:0.1.0
Vue:3
License:MIT
Last Updated:Oct 11, 2026
Links: GitHub  ·  npm
Maintainer: vaab

@0k/vue-period-picker

Purpose

@0k/vue-period-picker is a compact Vue 3 time-range picker for narrow sidebars. One full-width input opens a body-appended vue-datepicker-next range calendar with preset chips, previous/next controls, a Now button, and a current-period label. Hosts supply presets and own data fetching, refresh scheduling, routing, and theme values.

Install

npm install @0k/vue-period-picker

Peer dependencies: vue ^3.5.0 and vue3-gettext ^4.0.1. vue-datepicker-next 1.0.3 is a dependency, external to the package bundle. The package entry imports its CSS, which the host bundler resolves: the host imports only @0k/vue-period-picker/style.css explicitly, in any order relative to the package.

The type declarations use extensionless relative imports: host TypeScript configurations need "moduleResolution": "bundler", the setting of Vite projects. The entry's import of the datepicker CSS is a side-effect import; TypeScript 6 and later projects that check libraries (skipLibCheck: false) need CSS module types, such as Vite's vite/client.

Host tests running under Vitest must let Vite process the package, so that its CSS import is resolved rather than handed to Node:

// vite.config.ts (or vitest.config.ts)
test: { server: { deps: { inline: ['@0k/vue-period-picker'] } } }

Usage and gettext

Install the host's gettext plugin once. Merge the package's French messages with host messages by language, not just with a top-level object spread. Host messages take precedence in this example.

// main.ts
import { createApp } from 'vue';
import { createGettext } from 'vue3-gettext';
import { translations as periodTranslations } from '@0k/vue-period-picker';
import '@0k/vue-period-picker/style.css';
import App from './App.vue';
import hostTranslations from './locales/translations.json';

const gettext = createGettext({
  availableLanguages: { en: 'English', fr: 'Français' },
  defaultLanguage: 'fr',
  sourceCodeLanguage: 'en',
  translations: {
    ...periodTranslations,
    ...hostTranslations,
    fr: { ...periodTranslations.fr, ...hostTranslations.fr },
  },
});
createApp(App).use(gettext).mount('#app');
<script setup lang="ts">
import { ref } from 'vue';
import { PeriodPicker, type PeriodPreset, type PeriodValue } from '@0k/vue-period-picker';

const period = ref<PeriodValue>({ preset: '6h' });
const presets: PeriodPreset[] = [
  { id: '1h', seconds: 3600 },
  { id: '6h', seconds: 21600 },
  { id: 'quarter', unit: 'quarter' },
  { id: 'year', unit: 'year' },
];
</script>

<template>
  <PeriodPicker v-model="period" :presets="presets" />
</template>

The component is also the default export. Every generated string uses gettext; optional preset label strings are already translated by the host and override both the short chip text and long input/title label. Duration chips use compact labels such as 6h (6 h in French); their title retains the long label, such as Last 6 hours. Singular long labels are natural: Last hour / Dernière heure, not Last 1 hour. Calendar month and weekday names (English and French, weeks starting on Monday) are built into the package and passed to each picker: importing the package never changes vue-datepicker-next's global default locale, which other datepickers of the host rely on. Regional language codes (e.g. fr-FR) select the French calendar; unsupported calendar languages fall back to English. Hosts using regional gettext keys should also provide the package messages under that key.

Value contract and presets

All timestamps are Unix seconds, not milliseconds. to is exclusive.

type CalendarUnit = 'day' | 'week' | 'month' | 'quarter' | 'year';
interface DurationPreset { id: string; seconds: number; label?: string | undefined }
interface CalendarPreset { id: string; unit: CalendarUnit; label?: string | undefined }
type PeriodPreset = DurationPreset | CalendarPreset;
type PeriodValue = { preset: string } | { from: number; to: number };
interface PeriodWindow { start: number; end: number; live: boolean }
  • Preset ids must be unique; durations must be positive, finite seconds.
  • A duration emits { preset: id } and remains live. Each host refresh calls resolvePeriod(period, presets, now) with the current Unix time. The picker does not fetch data or start a host refresh timer.
  • A calendar chip emits the local calendar unit containing the current window's start, not today's unit. Weeks start Monday; quarters start January, April, July, and October. Calendar selections are fixed ranges.
  • Previous/next shifts an exactly calendar-aligned fixed range by its configured calendar unit. Otherwise it shifts by the range's width in seconds. Shifting a live duration produces a fixed range.
  • Now returns to the last duration selected by a chip or received through modelValue, including the value supplied on mount. Navigation to a fixed range preserves that remembered duration. If no duration has been selected or received, Now uses the first duration preset. It is hidden when no duration presets exist.
  • Datetime selection applies immediately, has hour/minute columns, and emits its selected end instant as exclusive. No Apply button is present. Picks emit whole minutes: seconds carried over from a live value are dropped, and min / max apply to those emitted minutes.
  • In datetime mode the footer's Select time / Select date toggle opens the current range's time panels without requiring new date selections (Choisir l'heure / Choisir la date in French). Selecting both dates opens the time panels automatically; closing resets to the date panel.
  • Date selection shows an inclusive end day, then emits the following local midnight as exclusive to. Conversion uses calendar days across DST, not an assumed 86400-second day. Incoming date-mode ranges display every local day intersecting the range; picking them normalizes to whole days.
  • Invalid/incomplete selections and from >= to do not emit.
  • Calendar bounds disable days with no allowed instant, and datetime bounds disable times outside the bounds. Bounds are inclusive for selection; date-mode choices cover whole intersecting days. Preset and navigation actions are intentionally not clamped to calendar bounds.

Component props and events

Prop Type Default
modelValue PeriodValue required
presets PeriodPreset[] required
type "date" or "datetime" 'datetime'
min, max number (Unix seconds) no bound
now () => number floored Date.now/1000

Event: update:modelValue(value: PeriodValue), for standard v-model.

modelValue must be valid for presets: like resolvePeriod, the component throws on a { preset } id that presets does not hold as a duration preset, or on a range with from >= to. Hosts restoring a saved value (URL, storage) after changing their preset list should check it with resolvePeriod first.

Test ids: period-picker, period-prev, period-next, period-now, period-preset-<id>, period-label, and period-time-toggle (datetime only).

Pure helpers

  • resolvePeriod(value, presets, now): PeriodWindow: unknown preset ids and calendar ids used as live preset values throw clear errors.
  • calendarRange(unit, at): { from, to }: local unit boundaries.
  • alignedUnit(range, presets): CalendarUnit | null: only configured calendar units count; largest exact match wins.
  • shiftPeriod(value, presets, now, direction: -1 | 1): { from, to }.
  • formatDuration(seconds, gettext) and formatPreset(preset, gettext).
  • formatPresetShort(preset, gettext): translated compact chip label using s, m, h, d, w, mo, or y in English; French uses s, min, h, j, sem, mois, and pluralized an / ans. Calendar unit labels and host-provided overrides are unchanged.
  • formatPeriod(value, presets, now, type, gettext): generated display label.
  • toPickerRange(range, type): [Date, Date] and fromPickerRange(dates, type): { from, to } | null: picker/API conversion.

PeriodGettext describes the helper's $gettext, $ngettext, and optional current language field. The helpers are only reachable through the package entry, which imports Vue, gettext and the datepicker: like the component, they need a browser (or a DOM such as jsdom), not plain Node or server rendering. translations exports compiled locale messages, including French plurals. Durations display the largest exactly divisible unit; months mean 30 days and years 365 days for duration labels, never for calendar arithmetic.

Theming and popup layout

Set custom properties on :root (or body): the popup teleports to body, so theme values set only on a sidebar ancestor do not reach it.

:root {
  --period-picker-accent: #2563eb;
  --period-picker-accent-soft: #e8efff;
  --period-picker-border: #cbd0d6;
  --period-picker-surface: #fff;
  --period-picker-text: #222;
  --period-picker-muted: #667085;
  --period-picker-radius: 4px;
  --period-picker-font-size: 13px;
  --period-picker-popup-z-index: 8600;
}

The input fills its container, including 260-320px sidebars. The upstream range calendar always renders two panels: no single-panel range option exists. Above 750px viewport width they sit side by side in a body-appended popup. At 750px and below they stack in a 264px popup, bounded by the viewport with vertical scrolling. Both datetime time panels remain usable; the package corrects upstream's fixed 496px datetime wrapper and 270px height.

Hour/minute columns preserve every minute and keep the popup open while editing either endpoint. Fixed time-picker-options lists are deliberately not used: they limit choices and close the popup on selection. Close by clicking outside.

The datepicker accesses document when imported: the public component entry is browser-only, not an SSR entry. The package is ESM only, like its vue3-gettext peer.

Development

npm install
npm run gettext:extract
npm run gettext:compile
npm run check
npm run test:browser

gettext:extract needs the GNU gettext tools (msgmerge, and msginit for a new language) on the PATH.

To try unreleased changes in a host, run ./autogen.sh, build, then link the checkout from the host with npm install --no-save /path/to/vue-period-picker. A linked checkout brings its own vue3-gettext: add resolve: { dedupe: ['vue3-gettext'] } to the host's Vite configuration, or the component fails with "Failed to inject gettext". ./autogen.sh writes the version into the tracked package.json and package-lock.json: restore their %%version%% placeholder before committing.

npm run check runs specification tests (in Europe/Paris, then the calendar tests again in America/Santiago, where DST skips midnight), strict typechecking, the Vite library build, and test:package. The build emits dist/index.js, dist/index.d.ts, and dist/style.css, plus support declarations and maps. test:package checks those artifacts and creates a real host-like Vite production bundle that consumes dist/index.js with its dependencies re-bundled, not externalized. test:browser builds that same production consumer and serves only its static output, asserting mount, interaction, and zero console/page errors. It requires Linux, system Chrome and a Python environment with Playwright. The temporary bundle and owned static HTTP server are cleaned up after the run; no development-server transform is used.

French PO, POT, and compiled JSON live under src/locales/. Regenerate JSON after modifying a catalog. Release scripts are vendored with pkg vendor. No release or publishing is performed by the check commands.

License

MIT, see LICENSE.

Changelog

0.1.0 (2026-10-09)

New
  • Provide a compact Vue period picker with presets and time selection. [Valentin Lab]

    One input fits a narrow side panel; its popup holds the preset chips, the previous and next buttons, Now, a range calendar and, in datetime mode, hour and minute columns. Presets are host-configured: live durations ending now (6h) or calendar units (day, week, month, quarter, year) that the previous and next buttons step through. Values are Unix seconds with an exclusive to; date mode shows an inclusive last day.

    Labels are English and translated with vue3-gettext; a French catalog is exported as translations, and calendar month and weekday names follow the gettext language.

Related Packages

nuxt-gettext

Translation module utilizing the gettext ecosystem

3,461 4
vue-gettext-extract

Generates gettext pot files for standard Vue projects

4,851 2
gettext-vue

Extract translatable strings from Vue files

13,854 16
vue-gettext-cli

Vue.js web application localization workflow command-line interface tool.

6,948 3
django-gettext-vue-loader

Loader for vue.js templates that allows to use django gettext template syntax

5,498 5

Version History

Version Released Vue License
0.1.0 ^3.5.0 MIT
Pre-releases (1)
Version Released License
0.0.0-stage