timezone-overlap-map 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Santiago Palenque
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,111 @@
1
- # Temporary Holding Version
1
+ # timezone-overlap-map
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A web component that shows your timezone next to your visitor's on a world map, and how your working hours overlap. Built with React and TypeScript, shipped as a standard custom element, so it works in any framework or in plain HTML.
4
+
5
+ - A world map with each zone's UTC-offset strip, a dot per city, and the live day/night line with the sun's position.
6
+ - Both clocks and a zone picker that starts at the visitor's zone.
7
+ - A 24-hour timeline in the visitor's local time, and the overlap as text for screen readers.
8
+
9
+ About 15 KB gzipped, map included, plus React.
10
+
11
+ ## Install
12
+
13
+ ```sh
14
+ npm install timezone-overlap-map react react-dom
15
+ ```
16
+
17
+ React is a peer dependency: your bundler provides it.
18
+
19
+ ## Usage
20
+
21
+ ```js
22
+ import 'timezone-overlap-map';
23
+ ```
24
+
25
+ ```html
26
+ <timezone-overlap-map
27
+ home-zone="America/Argentina/Buenos_Aires"
28
+ home-label="Santiago"
29
+ ></timezone-overlap-map>
30
+ ```
31
+
32
+ The visitor's zone comes from the browser (`Intl`), with no IP lookup. A selector lets them pick another one, for example a recruiter hiring for a team in a different zone.
33
+
34
+ ### Attributes
35
+
36
+ | Attribute | Default | |
37
+ |---|---|---|
38
+ | `home-zone` | `UTC` | IANA zone you work from |
39
+ | `home-label` | city of `home-zone` | Name shown for your side |
40
+ | `visitor-zone` | the browser's zone | Starting zone for the visitor |
41
+ | `work-start` | `9` | Start of working hours, local time in each zone |
42
+ | `work-end` | `18` | End of working hours (a value before `work-start` means a night shift) |
43
+ | `lang` | the closest `lang` up the tree | `en` or `es`; any other language falls back to English |
44
+
45
+ The language is read when the element renders and whenever its own attributes change; if you switch the page's `<html lang>` on the client, set `lang` on the element too.
46
+
47
+ Offsets are computed with `Intl` for the current date, so daylight saving time on either side is always right.
48
+
49
+ ### Events
50
+
51
+ `zonechange` fires when the visitor picks another zone:
52
+
53
+ ```js
54
+ el.addEventListener('zonechange', (event) => console.log(event.detail.zone));
55
+ ```
56
+
57
+ ### Theming
58
+
59
+ Styles live in the shadow DOM. Custom properties cross it, so set them on the element or any ancestor:
60
+
61
+ ```css
62
+ timezone-overlap-map {
63
+ --tom-fg: #e8e6e3;
64
+ --tom-muted: #9a9894;
65
+ --tom-track: #ffffff1a;
66
+ --tom-home: #4fd1c5;
67
+ --tom-visitor: #f6ad55;
68
+ --tom-overlap: #68d391;
69
+ --tom-land: #ffffff38;
70
+ --tom-night: #00000066;
71
+ --tom-sun: #fff7d6;
72
+ --tom-radius: 4px;
73
+ --tom-font: 14px/1.5 system-ui;
74
+ }
75
+ ```
76
+
77
+ The night and the sun have defaults for both light and dark pages, picked through the page's `color-scheme`.
78
+
79
+ ### Server rendering
80
+
81
+ Importing the package on the server is safe: the element registers only where `customElements` exists.
82
+
83
+ ## Development
84
+
85
+ ```sh
86
+ yarn
87
+ yarn dev # demo page at http://localhost:5173
88
+ yarn test
89
+ yarn build # dist/: ES module + type declarations
90
+ yarn geo # regenerate src/geo.ts (map outline + zone coordinates)
91
+ ```
92
+
93
+ ## Releasing
94
+
95
+ Push a `v*` tag matching `version` in `package.json`; the Release workflow tests, builds and publishes to npm with [provenance](https://docs.npmjs.com/generating-provenance-statements), through npm trusted publishing (no token stored in the repo).
96
+
97
+ ```sh
98
+ npm version patch # bumps package.json and creates the tag
99
+ git push --follow-tags
100
+ ```
101
+
102
+ ## Data
103
+
104
+ - Land outline: [Natural Earth](https://www.naturalearthdata.com/) 1:110m (public domain), via [world-atlas](https://github.com/topojson/world-atlas).
105
+ - Zone locations: [IANA tz database](https://www.iana.org/time-zones) (`zone.tab`, plus links so the names `Intl` returns resolve).
106
+
107
+ Both are converted once by `scripts/build-geo.mjs` into a compact SVG path and a coordinate table; the package has no runtime dependencies besides React.
108
+
109
+ ## License
110
+
111
+ MIT
@@ -0,0 +1,12 @@
1
+ import { type Lang } from './messages';
2
+ export type TimezoneOverlapProps = {
3
+ homeZone: string;
4
+ homeLabel?: string;
5
+ visitorZone: string;
6
+ /** Working hours, in each zone's local time. */
7
+ workStart: number;
8
+ workEnd: number;
9
+ lang: Lang;
10
+ onVisitorZoneChange?: (zone: string) => void;
11
+ };
12
+ export declare function TimezoneOverlap({ homeZone, homeLabel, visitorZone: initialVisitorZone, workStart, workEnd, lang, onVisitorZoneChange, }: TimezoneOverlapProps): import("react").JSX.Element;
@@ -0,0 +1,5 @@
1
+ export declare function WorldMap({ homeZone, visitorZone, now }: {
2
+ homeZone: string;
3
+ visitorZone: string;
4
+ now: Date;
5
+ }): import("react").JSX.Element;
@@ -0,0 +1,31 @@
1
+ export declare const TAG_NAME = "timezone-overlap-map";
2
+ export type ZoneChangeEvent = CustomEvent<{
3
+ zone: string;
4
+ }>;
5
+ declare const BaseElement: typeof HTMLElement;
6
+ /**
7
+ * `<timezone-overlap-map home-zone="America/Argentina/Buenos_Aires">`
8
+ *
9
+ * Attributes: `home-zone` (IANA name), `home-label`, `visitor-zone` (defaults to the
10
+ * browser's zone), `work-start` / `work-end` (local hours, default 9 and 18), `lang`
11
+ * (`en` or `es`; defaults to the closest `lang` up the tree, like the page's `<html lang>`).
12
+ * Fires `zonechange` (detail: `{ zone }`) when the visitor picks another zone.
13
+ */
14
+ export declare class TimezoneOverlapMapElement extends BaseElement {
15
+ #private;
16
+ static observedAttributes: readonly ["home-zone", "home-label", "visitor-zone", "work-start", "work-end", "lang"];
17
+ connectedCallback(): void;
18
+ disconnectedCallback(): void;
19
+ attributeChangedCallback(): void;
20
+ }
21
+ /** Registers the element under `tagName`; safe to call twice and on the server. */
22
+ export declare function defineTimezoneOverlapMap(tagName?: string): void;
23
+ declare global {
24
+ interface HTMLElementTagNameMap {
25
+ 'timezone-overlap-map': TimezoneOverlapMapElement;
26
+ }
27
+ interface HTMLElementEventMap {
28
+ zonechange: ZoneChangeEvent;
29
+ }
30
+ }
31
+ export {};
package/dist/geo.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ /** Equirectangular map: x = longitude + 180, y = 84 - latitude. */
2
+ export declare const MAP_NORTH = 84;
3
+ export declare const MAP_WIDTH = 360;
4
+ export declare const MAP_HEIGHT = 142;
5
+ export declare const LAND_PATH: string;
6
+ /** [latitude, longitude] of each zone's main location. */
7
+ export declare const ZONE_COORDS: Record<string, readonly [number, number]>;
@@ -0,0 +1,3 @@
1
+ export { TAG_NAME, TimezoneOverlapMapElement, defineTimezoneOverlapMap, type ZoneChangeEvent } from './element';
2
+ export { MESSAGES, resolveLang, type Lang, type Messages } from './messages';
3
+ export { offsetMinutes, overlap, type Overlap, type Segment } from './overlap';
@@ -0,0 +1,14 @@
1
+ export type Messages = {
2
+ /** Accessible name of the zone picker. */
3
+ zoneLabel: string;
4
+ /** `duration` is already formatted ("2 h 30 min"), `ranges` is in the visitor's local time. */
5
+ overlap: (duration: string, home: string, ranges: string) => string;
6
+ none: (home: string) => string;
7
+ };
8
+ export declare const MESSAGES: {
9
+ en: Messages;
10
+ es: Messages;
11
+ };
12
+ export type Lang = keyof typeof MESSAGES;
13
+ /** "es-AR" → "es"; anything without a translation → "en". */
14
+ export declare function resolveLang(tag: string | null | undefined): Lang;
@@ -0,0 +1,41 @@
1
+ export declare const DAY: number;
2
+ /** A half-open range of minutes within one day: [start, end). */
3
+ export type Segment = {
4
+ start: number;
5
+ end: number;
6
+ };
7
+ export type Overlap = {
8
+ /** Working hours of each zone and their intersection, in UTC minutes. */
9
+ home: Segment[];
10
+ visitor: Segment[];
11
+ both: Segment[];
12
+ /** Length of the overlap in minutes. */
13
+ minutes: number;
14
+ };
15
+ export declare function mod(n: number, m?: number): number;
16
+ export declare function isValidZone(zone: string): boolean;
17
+ /** Minutes ahead of UTC for `timeZone` at `date` (Buenos Aires → -180). */
18
+ export declare function offsetMinutes(timeZone: string, date: Date): number;
19
+ /** A range that may cross midnight, split into segments that don't, in order. */
20
+ export declare function toSegments(start: number, length: number): Segment[];
21
+ /**
22
+ * Moves segments by `minutes` (e.g. UTC → a zone's local time), re-splitting at midnight
23
+ * and joining pieces that now touch: a range split at UTC midnight is whole again locally.
24
+ */
25
+ export declare function shift(segments: Segment[], minutes: number): Segment[];
26
+ export declare function intersect(a: Segment[], b: Segment[]): Segment[];
27
+ export declare function totalMinutes(segments: Segment[]): number;
28
+ /**
29
+ * Ranges for display: a segment ending at midnight joins one starting at midnight,
30
+ * so 22:00–24:00 + 00:00–02:00 reads as 22:00–02:00 (`end` is then past DAY).
31
+ */
32
+ export declare function joinAcrossMidnight(segments: Segment[]): Segment[];
33
+ /** Working hours from `startHour` to `endHour` local time (a night shift may wrap), in UTC. */
34
+ export declare function workSegmentsUtc(zone: string, startHour: number, endHour: number, date: Date): Segment[];
35
+ export declare function overlap(homeZone: string, visitorZone: string, { workStart, workEnd, date }: {
36
+ workStart: number;
37
+ workEnd: number;
38
+ date: Date;
39
+ }): Overlap;
40
+ /** Sorted, with touching or overlapping segments joined. */
41
+ export declare function merge(segments: Segment[]): Segment[];
package/dist/sun.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ /** Latitude and longitude, in degrees, of the point with the sun at its zenith. */
2
+ export declare function subsolarPoint(date: Date): {
3
+ lat: number;
4
+ lon: number;
5
+ };
6
+ /** Latitude of the day/night line at `lon`, for the sun over `sun`. */
7
+ export declare function terminatorLat(lon: number, sun: {
8
+ lat: number;
9
+ lon: number;
10
+ }): number;
11
+ /** The night side as an SVG path in map coordinates (x = lon + 180, y = MAP_NORTH - lat). */
12
+ export declare function nightPath(date: Date, step?: number): string;