timezone-overlap-map 0.0.0-stage → 0.1.1
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 +21 -0
- package/README.md +110 -2
- package/dist/TimezoneOverlap.d.ts +12 -0
- package/dist/WorldMap.d.ts +5 -0
- package/dist/element.d.ts +31 -0
- package/dist/geo.d.ts +7 -0
- package/dist/index.d.ts +3 -0
- package/dist/messages.d.ts +14 -0
- package/dist/overlap.d.ts +41 -0
- package/dist/sun.d.ts +12 -0
- package/dist/timezone-overlap-map.js +956 -0
- package/dist/zones.d.ts +5 -0
- package/package.json +56 -4
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
|
-
#
|
|
1
|
+
# timezone-overlap-map
|
|
2
2
|
|
|
3
|
-
|
|
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,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]>;
|
package/dist/index.d.ts
ADDED
|
@@ -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;
|