@rsalianto/git-heatmap-vanilla 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/dist/index.d.mts +31 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.iife.js +331 -0
- package/dist/index.iife.js.map +1 -0
- package/dist/index.js +293 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +272 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +31 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { HeatmapData } from '@rsalianto/git-heatmap-core';
|
|
2
|
+
|
|
3
|
+
declare class GitHeatmapElement extends HTMLElement {
|
|
4
|
+
static get observedAttributes(): string[];
|
|
5
|
+
private _data;
|
|
6
|
+
private _fetchData;
|
|
7
|
+
private _shadow;
|
|
8
|
+
private _tooltip;
|
|
9
|
+
set data(value: HeatmapData);
|
|
10
|
+
get data(): HeatmapData | null;
|
|
11
|
+
set fetchData(fn: () => Promise<HeatmapData>);
|
|
12
|
+
constructor();
|
|
13
|
+
connectedCallback(): void;
|
|
14
|
+
attributeChangedCallback(name: string, _old: string | null, _val: string | null): void;
|
|
15
|
+
private get _cellSize();
|
|
16
|
+
private get _cellGap();
|
|
17
|
+
private get _cellRadius();
|
|
18
|
+
private get _step();
|
|
19
|
+
private get _showTotal();
|
|
20
|
+
private get _showLegend();
|
|
21
|
+
private get _showMonths();
|
|
22
|
+
private get _showDays();
|
|
23
|
+
private get _label();
|
|
24
|
+
private _load;
|
|
25
|
+
private _renderSkeleton;
|
|
26
|
+
private _renderError;
|
|
27
|
+
private _render;
|
|
28
|
+
private _attachListeners;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { GitHeatmapElement };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { HeatmapData } from '@rsalianto/git-heatmap-core';
|
|
2
|
+
|
|
3
|
+
declare class GitHeatmapElement extends HTMLElement {
|
|
4
|
+
static get observedAttributes(): string[];
|
|
5
|
+
private _data;
|
|
6
|
+
private _fetchData;
|
|
7
|
+
private _shadow;
|
|
8
|
+
private _tooltip;
|
|
9
|
+
set data(value: HeatmapData);
|
|
10
|
+
get data(): HeatmapData | null;
|
|
11
|
+
set fetchData(fn: () => Promise<HeatmapData>);
|
|
12
|
+
constructor();
|
|
13
|
+
connectedCallback(): void;
|
|
14
|
+
attributeChangedCallback(name: string, _old: string | null, _val: string | null): void;
|
|
15
|
+
private get _cellSize();
|
|
16
|
+
private get _cellGap();
|
|
17
|
+
private get _cellRadius();
|
|
18
|
+
private get _step();
|
|
19
|
+
private get _showTotal();
|
|
20
|
+
private get _showLegend();
|
|
21
|
+
private get _showMonths();
|
|
22
|
+
private get _showDays();
|
|
23
|
+
private get _label();
|
|
24
|
+
private _load;
|
|
25
|
+
private _renderSkeleton;
|
|
26
|
+
private _renderError;
|
|
27
|
+
private _render;
|
|
28
|
+
private _attachListeners;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { GitHeatmapElement };
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var GitHeatmapVanilla = (() => {
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
GitHeatmapElement: () => GitHeatmapElement
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// ../core/dist/index.mjs
|
|
28
|
+
var DEFAULT_LEVELS = [
|
|
29
|
+
{ threshold: 0, color: "var(--ghm-color-l0)", label: "No contributions" },
|
|
30
|
+
{ threshold: 1, color: "var(--ghm-color-l1)", label: "1\u20132 contributions" },
|
|
31
|
+
{ threshold: 3, color: "var(--ghm-color-l2)", label: "3\u20135 contributions" },
|
|
32
|
+
{ threshold: 6, color: "var(--ghm-color-l3)", label: "6\u201310 contributions" },
|
|
33
|
+
{ threshold: 11, color: "var(--ghm-color-l4)", label: "11+ contributions" }
|
|
34
|
+
];
|
|
35
|
+
var MONTHS = [
|
|
36
|
+
"Jan",
|
|
37
|
+
"Feb",
|
|
38
|
+
"Mar",
|
|
39
|
+
"Apr",
|
|
40
|
+
"May",
|
|
41
|
+
"Jun",
|
|
42
|
+
"Jul",
|
|
43
|
+
"Aug",
|
|
44
|
+
"Sep",
|
|
45
|
+
"Oct",
|
|
46
|
+
"Nov",
|
|
47
|
+
"Dec"
|
|
48
|
+
];
|
|
49
|
+
var DAY_LABELS = { 1: "Mon", 3: "Wed", 5: "Fri" };
|
|
50
|
+
function buildMonthLabels(weeks) {
|
|
51
|
+
const labels = [];
|
|
52
|
+
let lastMonth = -1;
|
|
53
|
+
for (let wi = 0; wi < weeks.length; wi++) {
|
|
54
|
+
const first = weeks[wi].days.find((d) => d.date);
|
|
55
|
+
if (!first) continue;
|
|
56
|
+
const m = (/* @__PURE__ */ new Date(first.date + "T00:00:00")).getMonth();
|
|
57
|
+
if (m !== lastMonth) {
|
|
58
|
+
labels.push({ label: MONTHS[m], col: wi });
|
|
59
|
+
lastMonth = m;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return labels;
|
|
63
|
+
}
|
|
64
|
+
var CELL = 10;
|
|
65
|
+
var GAP = 3;
|
|
66
|
+
var STEP = CELL + GAP;
|
|
67
|
+
|
|
68
|
+
// src/git-heatmap-element.ts
|
|
69
|
+
var BASE_STYLES = `
|
|
70
|
+
:host {
|
|
71
|
+
--ghm-color-l0: rgba(255,255,255,0.08);
|
|
72
|
+
--ghm-color-l1: #1c3d06;
|
|
73
|
+
--ghm-color-l2: #3a7510;
|
|
74
|
+
--ghm-color-l3: #6ab81e;
|
|
75
|
+
--ghm-color-l4: #aafd35;
|
|
76
|
+
--ghm-text: rgba(255,255,255,0.5);
|
|
77
|
+
--ghm-tooltip-bg: #1c2128;
|
|
78
|
+
--ghm-tooltip-border: rgba(255,255,255,0.1);
|
|
79
|
+
--ghm-tooltip-text: rgba(255,255,255,0.75);
|
|
80
|
+
--ghm-font: inherit;
|
|
81
|
+
--ghm-fs: 11px;
|
|
82
|
+
display: block;
|
|
83
|
+
position: relative;
|
|
84
|
+
width: 100%;
|
|
85
|
+
font-size: var(--ghm-fs);
|
|
86
|
+
font-family: var(--ghm-font);
|
|
87
|
+
color: var(--ghm-text);
|
|
88
|
+
user-select: none;
|
|
89
|
+
box-sizing: border-box;
|
|
90
|
+
}
|
|
91
|
+
* { box-sizing: border-box; }
|
|
92
|
+
.ghm-scroll { overflow-x: auto; overflow-y: visible; padding-bottom: 4px; }
|
|
93
|
+
.ghm-grid { display: inline-flex; flex-direction: column; }
|
|
94
|
+
.ghm-row { display: flex; }
|
|
95
|
+
.ghm-col { display: flex; flex-direction: column; }
|
|
96
|
+
.ghm-cell {
|
|
97
|
+
border-radius: 2px;
|
|
98
|
+
cursor: pointer;
|
|
99
|
+
transition: opacity 0.15s;
|
|
100
|
+
flex-shrink: 0;
|
|
101
|
+
}
|
|
102
|
+
.ghm-cell:hover { opacity: 0.7; }
|
|
103
|
+
.ghm-tooltip {
|
|
104
|
+
pointer-events: none;
|
|
105
|
+
position: absolute;
|
|
106
|
+
z-index: 50;
|
|
107
|
+
transform: translate(-50%, -100%);
|
|
108
|
+
background: var(--ghm-tooltip-bg);
|
|
109
|
+
border: 1px solid var(--ghm-tooltip-border);
|
|
110
|
+
color: var(--ghm-tooltip-text);
|
|
111
|
+
font-size: 0.9em;
|
|
112
|
+
border-radius: 4px;
|
|
113
|
+
padding: 3px 8px;
|
|
114
|
+
white-space: nowrap;
|
|
115
|
+
}
|
|
116
|
+
.ghm-skeleton { opacity: 0.4; }
|
|
117
|
+
`;
|
|
118
|
+
var GitHeatmapElement = class extends HTMLElement {
|
|
119
|
+
constructor() {
|
|
120
|
+
super();
|
|
121
|
+
this._data = null;
|
|
122
|
+
this._fetchData = null;
|
|
123
|
+
this._tooltip = null;
|
|
124
|
+
this._shadow = this.attachShadow({ mode: "open" });
|
|
125
|
+
}
|
|
126
|
+
static get observedAttributes() {
|
|
127
|
+
return ["api-url", "cell-size", "cell-gap", "cell-radius", "show-total", "show-legend", "show-month-labels", "show-day-labels", "label"];
|
|
128
|
+
}
|
|
129
|
+
set data(value) {
|
|
130
|
+
this._data = value;
|
|
131
|
+
this._render();
|
|
132
|
+
}
|
|
133
|
+
get data() {
|
|
134
|
+
return this._data;
|
|
135
|
+
}
|
|
136
|
+
set fetchData(fn) {
|
|
137
|
+
this._fetchData = fn;
|
|
138
|
+
this._load();
|
|
139
|
+
}
|
|
140
|
+
connectedCallback() {
|
|
141
|
+
const apiUrl = this.getAttribute("api-url");
|
|
142
|
+
if (apiUrl && !this._data) {
|
|
143
|
+
this._load();
|
|
144
|
+
} else if (this._data) {
|
|
145
|
+
this._render();
|
|
146
|
+
} else {
|
|
147
|
+
this._renderSkeleton();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
attributeChangedCallback(name, _old, _val) {
|
|
151
|
+
if (name === "api-url") {
|
|
152
|
+
this._load();
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (this._data) this._render();
|
|
156
|
+
}
|
|
157
|
+
get _cellSize() {
|
|
158
|
+
return parseInt(this.getAttribute("cell-size") ?? String(CELL), 10);
|
|
159
|
+
}
|
|
160
|
+
get _cellGap() {
|
|
161
|
+
return parseInt(this.getAttribute("cell-gap") ?? String(GAP), 10);
|
|
162
|
+
}
|
|
163
|
+
get _cellRadius() {
|
|
164
|
+
return parseInt(this.getAttribute("cell-radius") ?? "2", 10);
|
|
165
|
+
}
|
|
166
|
+
get _step() {
|
|
167
|
+
return this._cellSize + this._cellGap;
|
|
168
|
+
}
|
|
169
|
+
get _showTotal() {
|
|
170
|
+
return this.getAttribute("show-total") !== "false";
|
|
171
|
+
}
|
|
172
|
+
get _showLegend() {
|
|
173
|
+
return this.getAttribute("show-legend") !== "false";
|
|
174
|
+
}
|
|
175
|
+
get _showMonths() {
|
|
176
|
+
return this.getAttribute("show-month-labels") !== "false";
|
|
177
|
+
}
|
|
178
|
+
get _showDays() {
|
|
179
|
+
return this.getAttribute("show-day-labels") !== "false";
|
|
180
|
+
}
|
|
181
|
+
get _label() {
|
|
182
|
+
return this.getAttribute("label") ?? "Contribution heatmap";
|
|
183
|
+
}
|
|
184
|
+
async _load() {
|
|
185
|
+
const apiUrl = this.getAttribute("api-url");
|
|
186
|
+
const resolver = this._fetchData ?? (apiUrl ? () => fetch(apiUrl).then((r) => r.json()) : null);
|
|
187
|
+
if (!resolver) return;
|
|
188
|
+
this._renderSkeleton();
|
|
189
|
+
try {
|
|
190
|
+
this._data = await resolver();
|
|
191
|
+
this._render();
|
|
192
|
+
} catch {
|
|
193
|
+
this._renderError();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
_renderSkeleton() {
|
|
197
|
+
const cell = this._cellSize, gap = this._cellGap, step = this._step;
|
|
198
|
+
let cols = "";
|
|
199
|
+
for (let c = 0; c < 53; c++) {
|
|
200
|
+
let cells = "";
|
|
201
|
+
for (let r = 0; r < 7; r++) {
|
|
202
|
+
cells += `<div style="width:${cell}px;height:${cell}px;border-radius:${this._cellRadius}px;background:var(--ghm-color-l0);flex-shrink:0;"></div>`;
|
|
203
|
+
}
|
|
204
|
+
cols += `<div style="display:flex;flex-direction:column;gap:${gap}px;">${cells}</div>`;
|
|
205
|
+
}
|
|
206
|
+
this._shadow.innerHTML = `<style>${BASE_STYLES}</style>
|
|
207
|
+
<div aria-label="${this._label}" style="opacity:0.4;">
|
|
208
|
+
<div style="display:flex;gap:${gap}px;">${cols}</div>
|
|
209
|
+
</div>`;
|
|
210
|
+
}
|
|
211
|
+
_renderError() {
|
|
212
|
+
this._shadow.innerHTML = `<style>${BASE_STYLES}</style>
|
|
213
|
+
<p style="opacity:0.6;padding:1rem 0;">Could not load contribution data.</p>`;
|
|
214
|
+
}
|
|
215
|
+
_render() {
|
|
216
|
+
if (!this._data) return;
|
|
217
|
+
const d = this._data;
|
|
218
|
+
const cell = this._cellSize, gap = this._cellGap, r = this._cellRadius, step = this._step;
|
|
219
|
+
const levels = DEFAULT_LEVELS;
|
|
220
|
+
const monthLabels = this._showMonths ? buildMonthLabels(d.weeks) : [];
|
|
221
|
+
let monthRow = "";
|
|
222
|
+
if (this._showMonths) {
|
|
223
|
+
const cells = monthLabels.map(({ label, col }, idx) => {
|
|
224
|
+
const nextCol = monthLabels[idx + 1]?.col ?? d.weeks.length;
|
|
225
|
+
return `<div style="width:${(nextCol - col) * step}px;white-space:nowrap;font-size:0.9em;">${label}</div>`;
|
|
226
|
+
}).join("");
|
|
227
|
+
monthRow = `<div style="display:flex;margin-bottom:${gap}px;margin-left:${this._showDays ? 32 : 0}px;">${cells}</div>`;
|
|
228
|
+
}
|
|
229
|
+
let dayLabelCol = "";
|
|
230
|
+
if (this._showDays) {
|
|
231
|
+
const rows = Array.from(
|
|
232
|
+
{ length: 7 },
|
|
233
|
+
(_, dow) => `<div style="height:${cell}px;line-height:${cell}px;width:26px;text-align:right;padding-right:4px;font-size:0.9em;">${DAY_LABELS[dow] ?? ""}</div>`
|
|
234
|
+
).join("");
|
|
235
|
+
dayLabelCol = `<div style="display:flex;flex-direction:column;gap:${gap}px;margin-right:6px;">${rows}</div>`;
|
|
236
|
+
}
|
|
237
|
+
const weekCols = d.weeks.map((week, _wi) => {
|
|
238
|
+
const cells = Array.from({ length: 7 }, (_, dow) => {
|
|
239
|
+
const day = week.days[dow];
|
|
240
|
+
if (!day?.date) return `<div style="width:${cell}px;height:${cell}px;flex-shrink:0;"></div>`;
|
|
241
|
+
const lvl = day.level;
|
|
242
|
+
const tip = day.count === 0 ? `No contributions on ${day.date}` : `${day.count} contribution${day.count > 1 ? "s" : ""} on ${day.date}`;
|
|
243
|
+
return `<div
|
|
244
|
+
class="ghm-cell"
|
|
245
|
+
style="width:${cell}px;height:${cell}px;border-radius:${r}px;background:var(--ghm-color-l${lvl});"
|
|
246
|
+
data-date="${day.date}"
|
|
247
|
+
data-count="${day.count}"
|
|
248
|
+
aria-label="${tip}"
|
|
249
|
+
role="button"
|
|
250
|
+
tabindex="0"
|
|
251
|
+
></div>`;
|
|
252
|
+
}).join("");
|
|
253
|
+
return `<div style="display:flex;flex-direction:column;gap:${gap}px;">${cells}</div>`;
|
|
254
|
+
}).join("");
|
|
255
|
+
let legend = "";
|
|
256
|
+
if (this._showLegend) {
|
|
257
|
+
const cells = levels.map(
|
|
258
|
+
(lvl, i) => `<div style="width:${cell}px;height:${cell}px;border-radius:${r}px;background:var(--ghm-color-l${i});" title="${lvl.label}"></div>`
|
|
259
|
+
).join("");
|
|
260
|
+
legend = `<div style="display:flex;align-items:center;justify-content:flex-end;margin-top:8px;">
|
|
261
|
+
<div style="display:flex;align-items:center;gap:6px;font-size:0.9em;">
|
|
262
|
+
<span>Less</span>${cells}<span>More</span>
|
|
263
|
+
</div>
|
|
264
|
+
</div>`;
|
|
265
|
+
}
|
|
266
|
+
const total = this._showTotal ? `<p style="margin-bottom:12px;color:var(--ghm-text);">${d.totalContributions.toLocaleString()} contributions in the last year</p>` : "";
|
|
267
|
+
this._shadow.innerHTML = `<style>${BASE_STYLES}</style>
|
|
268
|
+
<div aria-label="${this._label}" style="position:relative;">
|
|
269
|
+
${total}
|
|
270
|
+
<div class="ghm-scroll">
|
|
271
|
+
<div class="ghm-grid" style="min-width:${d.weeks.length * step}px;">
|
|
272
|
+
${monthRow}
|
|
273
|
+
<div style="display:flex;">
|
|
274
|
+
${dayLabelCol}
|
|
275
|
+
<div style="display:flex;gap:${gap}px;">${weekCols}</div>
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
<div id="ghm-mobile-tip" style="display:none;margin-top:8px;font-size:0.9em;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.1);border-radius:4px;padding:6px 12px;"></div>
|
|
280
|
+
${legend}
|
|
281
|
+
<div class="ghm-tooltip" id="ghm-tt" style="display:none;"></div>
|
|
282
|
+
</div>`;
|
|
283
|
+
this._attachListeners();
|
|
284
|
+
const scrollEl = this._shadow.querySelector(".ghm-scroll");
|
|
285
|
+
if (scrollEl) scrollEl.scrollLeft = scrollEl.scrollWidth;
|
|
286
|
+
}
|
|
287
|
+
_attachListeners() {
|
|
288
|
+
const wrapper = this._shadow.firstElementChild;
|
|
289
|
+
const tt = this._shadow.getElementById("ghm-tt");
|
|
290
|
+
const mobileTip = this._shadow.getElementById("ghm-mobile-tip");
|
|
291
|
+
let activeTapDate = "";
|
|
292
|
+
this._shadow.querySelectorAll(".ghm-cell").forEach((el) => {
|
|
293
|
+
const cell = el;
|
|
294
|
+
const date = cell.dataset["date"] ?? "";
|
|
295
|
+
const count = parseInt(cell.dataset["count"] ?? "0", 10);
|
|
296
|
+
const tip = count === 0 ? `No contributions on ${date}` : `${count} contribution${count > 1 ? "s" : ""} on ${date}`;
|
|
297
|
+
cell.addEventListener("mouseenter", (e) => {
|
|
298
|
+
const r = cell.getBoundingClientRect();
|
|
299
|
+
const wr = wrapper.getBoundingClientRect();
|
|
300
|
+
tt.textContent = tip;
|
|
301
|
+
tt.style.left = `${r.left - wr.left + this._cellSize / 2}px`;
|
|
302
|
+
tt.style.top = `${r.top - wr.top - 6}px`;
|
|
303
|
+
tt.style.display = "block";
|
|
304
|
+
});
|
|
305
|
+
cell.addEventListener("mouseleave", () => {
|
|
306
|
+
tt.style.display = "none";
|
|
307
|
+
});
|
|
308
|
+
cell.addEventListener("click", () => {
|
|
309
|
+
if (activeTapDate === date) {
|
|
310
|
+
activeTapDate = "";
|
|
311
|
+
mobileTip.style.display = "none";
|
|
312
|
+
} else {
|
|
313
|
+
activeTapDate = date;
|
|
314
|
+
mobileTip.textContent = tip;
|
|
315
|
+
mobileTip.style.display = "block";
|
|
316
|
+
}
|
|
317
|
+
this.dispatchEvent(new CustomEvent("day-click", {
|
|
318
|
+
detail: { date, count },
|
|
319
|
+
bubbles: true,
|
|
320
|
+
composed: true
|
|
321
|
+
}));
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
if (typeof customElements !== "undefined" && !customElements.get("git-heatmap")) {
|
|
327
|
+
customElements.define("git-heatmap", GitHeatmapElement);
|
|
328
|
+
}
|
|
329
|
+
return __toCommonJS(index_exports);
|
|
330
|
+
})();
|
|
331
|
+
//# sourceMappingURL=index.iife.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../../core/src/types.ts","../../core/src/levels.ts","../../core/src/normalize.ts","../../core/src/calendar.ts","../src/git-heatmap-element.ts"],"sourcesContent":["export { GitHeatmapElement } from \"./git-heatmap-element\";\n","export interface HeatmapDay {\n date: string;\n count: number;\n level: 0 | 1 | 2 | 3 | 4;\n}\n\nexport interface HeatmapWeek {\n days: HeatmapDay[];\n}\n\nexport interface HeatmapData {\n totalContributions: number;\n weeks: HeatmapWeek[];\n source: \"github\" | \"gitlab\" | \"manual\";\n}\n\nexport interface LevelConfig {\n threshold: number;\n color: string;\n label: string;\n}\n\nexport const DEFAULT_LEVELS: LevelConfig[] = [\n { threshold: 0, color: \"var(--ghm-color-l0)\", label: \"No contributions\" },\n { threshold: 1, color: \"var(--ghm-color-l1)\", label: \"1–2 contributions\" },\n { threshold: 3, color: \"var(--ghm-color-l2)\", label: \"3–5 contributions\" },\n { threshold: 6, color: \"var(--ghm-color-l3)\", label: \"6–10 contributions\" },\n { threshold: 11, color: \"var(--ghm-color-l4)\", label: \"11+ contributions\" },\n];\n\nexport interface HeatmapTheme {\n colorL0: string;\n colorL1: string;\n colorL2: string;\n colorL3: string;\n colorL4: string;\n textColor: string;\n tooltipBg: string;\n tooltipBorderColor: string;\n tooltipTextColor: string;\n fontFamily: string;\n fontSize: string;\n}\n\nexport const DEFAULT_THEME: HeatmapTheme = {\n colorL0: \"rgba(255,255,255,0.08)\",\n colorL1: \"#1c3d06\",\n colorL2: \"#3a7510\",\n colorL3: \"#6ab81e\",\n colorL4: \"#aafd35\",\n textColor: \"rgba(255,255,255,0.5)\",\n tooltipBg: \"#1c2128\",\n tooltipBorderColor: \"rgba(255,255,255,0.1)\",\n tooltipTextColor: \"rgba(255,255,255,0.75)\",\n fontFamily: \"inherit\",\n fontSize: \"11px\",\n};\n\nexport interface GitHubFetchOptions {\n username: string;\n token: string;\n from?: string;\n to?: string;\n}\n\nexport interface GitLabFetchOptions {\n username: string;\n token?: string;\n baseUrl?: string;\n}\n","import type { LevelConfig } from \"./types\";\n\nexport function getLevel(count: number, levels: LevelConfig[]): 0 | 1 | 2 | 3 | 4 {\n let result = 0;\n for (let i = 0; i < levels.length && i < 5; i++) {\n if (count >= levels[i].threshold) result = i;\n }\n return result as 0 | 1 | 2 | 3 | 4;\n}\n","import type { HeatmapData, HeatmapWeek, LevelConfig } from \"./types\";\nimport { DEFAULT_LEVELS } from \"./types\";\nimport { getLevel } from \"./levels\";\n\nexport interface GitHubRawCalendar {\n totalContributions: number;\n weeks: Array<{\n contributionDays: Array<{\n date: string;\n contributionCount: number;\n }>;\n }>;\n}\n\nexport type GitLabRawCalendar = Record<string, number>;\n\nexport function normalizeGitHub(\n raw: GitHubRawCalendar,\n levels: LevelConfig[] = DEFAULT_LEVELS\n): HeatmapData {\n const weeks: HeatmapWeek[] = raw.weeks.map((w) => ({\n days: Array.from({ length: 7 }, (_, dow) => {\n const day = w.contributionDays[dow];\n const count = day?.contributionCount ?? 0;\n return { date: day?.date ?? \"\", count, level: getLevel(count, levels) };\n }),\n }));\n return { totalContributions: raw.totalContributions, weeks, source: \"github\" };\n}\n\nexport function normalizeGitLab(\n raw: GitLabRawCalendar,\n levels: LevelConfig[] = DEFAULT_LEVELS\n): HeatmapData {\n const entries = Object.entries(raw).sort(([a], [b]) => a.localeCompare(b));\n const weeks: HeatmapWeek[] = [];\n let currentWeek: HeatmapWeek | null = null;\n\n for (const [date, count] of entries) {\n const dow = new Date(date + \"T00:00:00\").getDay();\n if (dow === 0 || currentWeek === null) {\n currentWeek = { days: [] };\n weeks.push(currentWeek);\n }\n currentWeek.days[dow] = { date, count, level: getLevel(count, levels) };\n }\n\n const total = entries.reduce((s, [, c]) => s + c, 0);\n return { totalContributions: total, weeks, source: \"gitlab\" };\n}\n\nexport function normalizeManual(\n data: Array<{ date: string; count: number }>,\n levels: LevelConfig[] = DEFAULT_LEVELS\n): HeatmapData {\n const raw: GitLabRawCalendar = Object.fromEntries(data.map((d) => [d.date, d.count]));\n const result = normalizeGitLab(raw, levels);\n return { ...result, source: \"manual\" };\n}\n","export const MONTHS = [\n \"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\",\n \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\",\n];\n\nexport const DAY_LABELS: Record<number, string> = { 1: \"Mon\", 3: \"Wed\", 5: \"Fri\" };\n\nexport interface MonthLabel {\n label: string;\n col: number;\n}\n\nexport function buildMonthLabels(weeks: { days: { date: string }[] }[]): MonthLabel[] {\n const labels: MonthLabel[] = [];\n let lastMonth = -1;\n for (let wi = 0; wi < weeks.length; wi++) {\n const first = weeks[wi].days.find((d) => d.date);\n if (!first) continue;\n const m = new Date(first.date + \"T00:00:00\").getMonth();\n if (m !== lastMonth) {\n labels.push({ label: MONTHS[m], col: wi });\n lastMonth = m;\n }\n }\n return labels;\n}\n\nexport const CELL = 10;\nexport const GAP = 3;\nexport const STEP = CELL + GAP;\n","import {\n buildMonthLabels, CELL, GAP, STEP, DAY_LABELS,\n DEFAULT_LEVELS, DEFAULT_THEME, getLevel,\n} from \"@rsalianto/git-heatmap-core\";\nimport type { HeatmapData, HeatmapDay, LevelConfig } from \"@rsalianto/git-heatmap-core\";\n\nconst BASE_STYLES = `\n :host {\n --ghm-color-l0: rgba(255,255,255,0.08);\n --ghm-color-l1: #1c3d06;\n --ghm-color-l2: #3a7510;\n --ghm-color-l3: #6ab81e;\n --ghm-color-l4: #aafd35;\n --ghm-text: rgba(255,255,255,0.5);\n --ghm-tooltip-bg: #1c2128;\n --ghm-tooltip-border: rgba(255,255,255,0.1);\n --ghm-tooltip-text: rgba(255,255,255,0.75);\n --ghm-font: inherit;\n --ghm-fs: 11px;\n display: block;\n position: relative;\n width: 100%;\n font-size: var(--ghm-fs);\n font-family: var(--ghm-font);\n color: var(--ghm-text);\n user-select: none;\n box-sizing: border-box;\n }\n * { box-sizing: border-box; }\n .ghm-scroll { overflow-x: auto; overflow-y: visible; padding-bottom: 4px; }\n .ghm-grid { display: inline-flex; flex-direction: column; }\n .ghm-row { display: flex; }\n .ghm-col { display: flex; flex-direction: column; }\n .ghm-cell {\n border-radius: 2px;\n cursor: pointer;\n transition: opacity 0.15s;\n flex-shrink: 0;\n }\n .ghm-cell:hover { opacity: 0.7; }\n .ghm-tooltip {\n pointer-events: none;\n position: absolute;\n z-index: 50;\n transform: translate(-50%, -100%);\n background: var(--ghm-tooltip-bg);\n border: 1px solid var(--ghm-tooltip-border);\n color: var(--ghm-tooltip-text);\n font-size: 0.9em;\n border-radius: 4px;\n padding: 3px 8px;\n white-space: nowrap;\n }\n .ghm-skeleton { opacity: 0.4; }\n`;\n\nexport class GitHeatmapElement extends HTMLElement {\n static get observedAttributes() {\n return [\"api-url\", \"cell-size\", \"cell-gap\", \"cell-radius\", \"show-total\", \"show-legend\", \"show-month-labels\", \"show-day-labels\", \"label\"];\n }\n\n private _data: HeatmapData | null = null;\n private _fetchData: (() => Promise<HeatmapData>) | null = null;\n private _shadow: ShadowRoot;\n private _tooltip: HTMLDivElement | null = null;\n\n set data(value: HeatmapData) { this._data = value; this._render(); }\n get data(): HeatmapData | null { return this._data; }\n\n set fetchData(fn: () => Promise<HeatmapData>) { this._fetchData = fn; this._load(); }\n\n constructor() {\n super();\n this._shadow = this.attachShadow({ mode: \"open\" });\n }\n\n connectedCallback() {\n const apiUrl = this.getAttribute(\"api-url\");\n if (apiUrl && !this._data) {\n this._load();\n } else if (this._data) {\n this._render();\n } else {\n this._renderSkeleton();\n }\n }\n\n attributeChangedCallback(name: string, _old: string | null, _val: string | null) {\n if (name === \"api-url\") { this._load(); return; }\n if (this._data) this._render();\n }\n\n private get _cellSize() { return parseInt(this.getAttribute(\"cell-size\") ?? String(CELL), 10); }\n private get _cellGap() { return parseInt(this.getAttribute(\"cell-gap\") ?? String(GAP), 10); }\n private get _cellRadius(){ return parseInt(this.getAttribute(\"cell-radius\") ?? \"2\", 10); }\n private get _step() { return this._cellSize + this._cellGap; }\n private get _showTotal() { return this.getAttribute(\"show-total\") !== \"false\"; }\n private get _showLegend(){ return this.getAttribute(\"show-legend\") !== \"false\"; }\n private get _showMonths(){ return this.getAttribute(\"show-month-labels\") !== \"false\"; }\n private get _showDays() { return this.getAttribute(\"show-day-labels\") !== \"false\"; }\n private get _label() { return this.getAttribute(\"label\") ?? \"Contribution heatmap\"; }\n\n private async _load() {\n const apiUrl = this.getAttribute(\"api-url\");\n const resolver = this._fetchData\n ?? (apiUrl ? () => fetch(apiUrl).then((r) => r.json()) : null);\n if (!resolver) return;\n\n this._renderSkeleton();\n try {\n this._data = await resolver() as HeatmapData;\n this._render();\n } catch {\n this._renderError();\n }\n }\n\n private _renderSkeleton() {\n const cell = this._cellSize, gap = this._cellGap, step = this._step;\n let cols = \"\";\n for (let c = 0; c < 53; c++) {\n let cells = \"\";\n for (let r = 0; r < 7; r++) {\n cells += `<div style=\"width:${cell}px;height:${cell}px;border-radius:${this._cellRadius}px;background:var(--ghm-color-l0);flex-shrink:0;\"></div>`;\n }\n cols += `<div style=\"display:flex;flex-direction:column;gap:${gap}px;\">${cells}</div>`;\n }\n this._shadow.innerHTML = `<style>${BASE_STYLES}</style>\n <div aria-label=\"${this._label}\" style=\"opacity:0.4;\">\n <div style=\"display:flex;gap:${gap}px;\">${cols}</div>\n </div>`;\n }\n\n private _renderError() {\n this._shadow.innerHTML = `<style>${BASE_STYLES}</style>\n <p style=\"opacity:0.6;padding:1rem 0;\">Could not load contribution data.</p>`;\n }\n\n private _render() {\n if (!this._data) return;\n const d = this._data;\n const cell = this._cellSize, gap = this._cellGap, r = this._cellRadius, step = this._step;\n const levels: LevelConfig[] = DEFAULT_LEVELS;\n\n const monthLabels = this._showMonths ? buildMonthLabels(d.weeks) : [];\n\n let monthRow = \"\";\n if (this._showMonths) {\n const cells = monthLabels.map(({ label, col }, idx) => {\n const nextCol = monthLabels[idx + 1]?.col ?? d.weeks.length;\n return `<div style=\"width:${(nextCol - col) * step}px;white-space:nowrap;font-size:0.9em;\">${label}</div>`;\n }).join(\"\");\n monthRow = `<div style=\"display:flex;margin-bottom:${gap}px;margin-left:${this._showDays ? 32 : 0}px;\">${cells}</div>`;\n }\n\n let dayLabelCol = \"\";\n if (this._showDays) {\n const rows = Array.from({ length: 7 }, (_, dow) =>\n `<div style=\"height:${cell}px;line-height:${cell}px;width:26px;text-align:right;padding-right:4px;font-size:0.9em;\">${DAY_LABELS[dow] ?? \"\"}</div>`\n ).join(\"\");\n dayLabelCol = `<div style=\"display:flex;flex-direction:column;gap:${gap}px;margin-right:6px;\">${rows}</div>`;\n }\n\n const weekCols = d.weeks.map((week, _wi) => {\n const cells = Array.from({ length: 7 }, (_, dow) => {\n const day = week.days[dow];\n if (!day?.date) return `<div style=\"width:${cell}px;height:${cell}px;flex-shrink:0;\"></div>`;\n const lvl = day.level;\n const tip = day.count === 0\n ? `No contributions on ${day.date}`\n : `${day.count} contribution${day.count > 1 ? \"s\" : \"\"} on ${day.date}`;\n return `<div\n class=\"ghm-cell\"\n style=\"width:${cell}px;height:${cell}px;border-radius:${r}px;background:var(--ghm-color-l${lvl});\"\n data-date=\"${day.date}\"\n data-count=\"${day.count}\"\n aria-label=\"${tip}\"\n role=\"button\"\n tabindex=\"0\"\n ></div>`;\n }).join(\"\");\n return `<div style=\"display:flex;flex-direction:column;gap:${gap}px;\">${cells}</div>`;\n }).join(\"\");\n\n let legend = \"\";\n if (this._showLegend) {\n const cells = levels.map((lvl, i) =>\n `<div style=\"width:${cell}px;height:${cell}px;border-radius:${r}px;background:var(--ghm-color-l${i});\" title=\"${lvl.label}\"></div>`\n ).join(\"\");\n legend = `<div style=\"display:flex;align-items:center;justify-content:flex-end;margin-top:8px;\">\n <div style=\"display:flex;align-items:center;gap:6px;font-size:0.9em;\">\n <span>Less</span>${cells}<span>More</span>\n </div>\n </div>`;\n }\n\n const total = this._showTotal\n ? `<p style=\"margin-bottom:12px;color:var(--ghm-text);\">${d.totalContributions.toLocaleString()} contributions in the last year</p>`\n : \"\";\n\n this._shadow.innerHTML = `<style>${BASE_STYLES}</style>\n <div aria-label=\"${this._label}\" style=\"position:relative;\">\n ${total}\n <div class=\"ghm-scroll\">\n <div class=\"ghm-grid\" style=\"min-width:${d.weeks.length * step}px;\">\n ${monthRow}\n <div style=\"display:flex;\">\n ${dayLabelCol}\n <div style=\"display:flex;gap:${gap}px;\">${weekCols}</div>\n </div>\n </div>\n </div>\n <div id=\"ghm-mobile-tip\" style=\"display:none;margin-top:8px;font-size:0.9em;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.1);border-radius:4px;padding:6px 12px;\"></div>\n ${legend}\n <div class=\"ghm-tooltip\" id=\"ghm-tt\" style=\"display:none;\"></div>\n </div>`;\n\n this._attachListeners();\n\n // auto-scroll to end\n const scrollEl = this._shadow.querySelector(\".ghm-scroll\") as HTMLElement | null;\n if (scrollEl) scrollEl.scrollLeft = scrollEl.scrollWidth;\n }\n\n private _attachListeners() {\n const wrapper = this._shadow.firstElementChild as HTMLElement;\n const tt = this._shadow.getElementById(\"ghm-tt\") as HTMLDivElement;\n const mobileTip = this._shadow.getElementById(\"ghm-mobile-tip\") as HTMLDivElement;\n let activeTapDate = \"\";\n\n this._shadow.querySelectorAll(\".ghm-cell\").forEach((el) => {\n const cell = el as HTMLElement;\n const date = cell.dataset[\"date\"] ?? \"\";\n const count = parseInt(cell.dataset[\"count\"] ?? \"0\", 10);\n const tip = count === 0\n ? `No contributions on ${date}`\n : `${count} contribution${count > 1 ? \"s\" : \"\"} on ${date}`;\n\n cell.addEventListener(\"mouseenter\", (e) => {\n const r = cell.getBoundingClientRect();\n const wr = wrapper.getBoundingClientRect();\n tt.textContent = tip;\n tt.style.left = `${r.left - wr.left + this._cellSize / 2}px`;\n tt.style.top = `${r.top - wr.top - 6}px`;\n tt.style.display = \"block\";\n });\n cell.addEventListener(\"mouseleave\", () => { tt.style.display = \"none\"; });\n cell.addEventListener(\"click\", () => {\n if (activeTapDate === date) {\n activeTapDate = \"\";\n mobileTip.style.display = \"none\";\n } else {\n activeTapDate = date;\n mobileTip.textContent = tip;\n mobileTip.style.display = \"block\";\n }\n this.dispatchEvent(new CustomEvent(\"day-click\", {\n detail: { date, count },\n bubbles: true,\n composed: true,\n }));\n });\n });\n }\n}\n\nif (typeof customElements !== \"undefined\" && !customElements.get(\"git-heatmap\")) {\n customElements.define(\"git-heatmap\", GitHeatmapElement);\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
GitHeatmapElement: () => GitHeatmapElement
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
27
|
+
// src/git-heatmap-element.ts
|
|
28
|
+
var import_git_heatmap_core = require("@rsalianto/git-heatmap-core");
|
|
29
|
+
var BASE_STYLES = `
|
|
30
|
+
:host {
|
|
31
|
+
--ghm-color-l0: rgba(255,255,255,0.08);
|
|
32
|
+
--ghm-color-l1: #1c3d06;
|
|
33
|
+
--ghm-color-l2: #3a7510;
|
|
34
|
+
--ghm-color-l3: #6ab81e;
|
|
35
|
+
--ghm-color-l4: #aafd35;
|
|
36
|
+
--ghm-text: rgba(255,255,255,0.5);
|
|
37
|
+
--ghm-tooltip-bg: #1c2128;
|
|
38
|
+
--ghm-tooltip-border: rgba(255,255,255,0.1);
|
|
39
|
+
--ghm-tooltip-text: rgba(255,255,255,0.75);
|
|
40
|
+
--ghm-font: inherit;
|
|
41
|
+
--ghm-fs: 11px;
|
|
42
|
+
display: block;
|
|
43
|
+
position: relative;
|
|
44
|
+
width: 100%;
|
|
45
|
+
font-size: var(--ghm-fs);
|
|
46
|
+
font-family: var(--ghm-font);
|
|
47
|
+
color: var(--ghm-text);
|
|
48
|
+
user-select: none;
|
|
49
|
+
box-sizing: border-box;
|
|
50
|
+
}
|
|
51
|
+
* { box-sizing: border-box; }
|
|
52
|
+
.ghm-scroll { overflow-x: auto; overflow-y: visible; padding-bottom: 4px; }
|
|
53
|
+
.ghm-grid { display: inline-flex; flex-direction: column; }
|
|
54
|
+
.ghm-row { display: flex; }
|
|
55
|
+
.ghm-col { display: flex; flex-direction: column; }
|
|
56
|
+
.ghm-cell {
|
|
57
|
+
border-radius: 2px;
|
|
58
|
+
cursor: pointer;
|
|
59
|
+
transition: opacity 0.15s;
|
|
60
|
+
flex-shrink: 0;
|
|
61
|
+
}
|
|
62
|
+
.ghm-cell:hover { opacity: 0.7; }
|
|
63
|
+
.ghm-tooltip {
|
|
64
|
+
pointer-events: none;
|
|
65
|
+
position: absolute;
|
|
66
|
+
z-index: 50;
|
|
67
|
+
transform: translate(-50%, -100%);
|
|
68
|
+
background: var(--ghm-tooltip-bg);
|
|
69
|
+
border: 1px solid var(--ghm-tooltip-border);
|
|
70
|
+
color: var(--ghm-tooltip-text);
|
|
71
|
+
font-size: 0.9em;
|
|
72
|
+
border-radius: 4px;
|
|
73
|
+
padding: 3px 8px;
|
|
74
|
+
white-space: nowrap;
|
|
75
|
+
}
|
|
76
|
+
.ghm-skeleton { opacity: 0.4; }
|
|
77
|
+
`;
|
|
78
|
+
var GitHeatmapElement = class extends HTMLElement {
|
|
79
|
+
constructor() {
|
|
80
|
+
super();
|
|
81
|
+
this._data = null;
|
|
82
|
+
this._fetchData = null;
|
|
83
|
+
this._tooltip = null;
|
|
84
|
+
this._shadow = this.attachShadow({ mode: "open" });
|
|
85
|
+
}
|
|
86
|
+
static get observedAttributes() {
|
|
87
|
+
return ["api-url", "cell-size", "cell-gap", "cell-radius", "show-total", "show-legend", "show-month-labels", "show-day-labels", "label"];
|
|
88
|
+
}
|
|
89
|
+
set data(value) {
|
|
90
|
+
this._data = value;
|
|
91
|
+
this._render();
|
|
92
|
+
}
|
|
93
|
+
get data() {
|
|
94
|
+
return this._data;
|
|
95
|
+
}
|
|
96
|
+
set fetchData(fn) {
|
|
97
|
+
this._fetchData = fn;
|
|
98
|
+
this._load();
|
|
99
|
+
}
|
|
100
|
+
connectedCallback() {
|
|
101
|
+
const apiUrl = this.getAttribute("api-url");
|
|
102
|
+
if (apiUrl && !this._data) {
|
|
103
|
+
this._load();
|
|
104
|
+
} else if (this._data) {
|
|
105
|
+
this._render();
|
|
106
|
+
} else {
|
|
107
|
+
this._renderSkeleton();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
attributeChangedCallback(name, _old, _val) {
|
|
111
|
+
if (name === "api-url") {
|
|
112
|
+
this._load();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (this._data) this._render();
|
|
116
|
+
}
|
|
117
|
+
get _cellSize() {
|
|
118
|
+
return parseInt(this.getAttribute("cell-size") ?? String(import_git_heatmap_core.CELL), 10);
|
|
119
|
+
}
|
|
120
|
+
get _cellGap() {
|
|
121
|
+
return parseInt(this.getAttribute("cell-gap") ?? String(import_git_heatmap_core.GAP), 10);
|
|
122
|
+
}
|
|
123
|
+
get _cellRadius() {
|
|
124
|
+
return parseInt(this.getAttribute("cell-radius") ?? "2", 10);
|
|
125
|
+
}
|
|
126
|
+
get _step() {
|
|
127
|
+
return this._cellSize + this._cellGap;
|
|
128
|
+
}
|
|
129
|
+
get _showTotal() {
|
|
130
|
+
return this.getAttribute("show-total") !== "false";
|
|
131
|
+
}
|
|
132
|
+
get _showLegend() {
|
|
133
|
+
return this.getAttribute("show-legend") !== "false";
|
|
134
|
+
}
|
|
135
|
+
get _showMonths() {
|
|
136
|
+
return this.getAttribute("show-month-labels") !== "false";
|
|
137
|
+
}
|
|
138
|
+
get _showDays() {
|
|
139
|
+
return this.getAttribute("show-day-labels") !== "false";
|
|
140
|
+
}
|
|
141
|
+
get _label() {
|
|
142
|
+
return this.getAttribute("label") ?? "Contribution heatmap";
|
|
143
|
+
}
|
|
144
|
+
async _load() {
|
|
145
|
+
const apiUrl = this.getAttribute("api-url");
|
|
146
|
+
const resolver = this._fetchData ?? (apiUrl ? () => fetch(apiUrl).then((r) => r.json()) : null);
|
|
147
|
+
if (!resolver) return;
|
|
148
|
+
this._renderSkeleton();
|
|
149
|
+
try {
|
|
150
|
+
this._data = await resolver();
|
|
151
|
+
this._render();
|
|
152
|
+
} catch {
|
|
153
|
+
this._renderError();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
_renderSkeleton() {
|
|
157
|
+
const cell = this._cellSize, gap = this._cellGap, step = this._step;
|
|
158
|
+
let cols = "";
|
|
159
|
+
for (let c = 0; c < 53; c++) {
|
|
160
|
+
let cells = "";
|
|
161
|
+
for (let r = 0; r < 7; r++) {
|
|
162
|
+
cells += `<div style="width:${cell}px;height:${cell}px;border-radius:${this._cellRadius}px;background:var(--ghm-color-l0);flex-shrink:0;"></div>`;
|
|
163
|
+
}
|
|
164
|
+
cols += `<div style="display:flex;flex-direction:column;gap:${gap}px;">${cells}</div>`;
|
|
165
|
+
}
|
|
166
|
+
this._shadow.innerHTML = `<style>${BASE_STYLES}</style>
|
|
167
|
+
<div aria-label="${this._label}" style="opacity:0.4;">
|
|
168
|
+
<div style="display:flex;gap:${gap}px;">${cols}</div>
|
|
169
|
+
</div>`;
|
|
170
|
+
}
|
|
171
|
+
_renderError() {
|
|
172
|
+
this._shadow.innerHTML = `<style>${BASE_STYLES}</style>
|
|
173
|
+
<p style="opacity:0.6;padding:1rem 0;">Could not load contribution data.</p>`;
|
|
174
|
+
}
|
|
175
|
+
_render() {
|
|
176
|
+
if (!this._data) return;
|
|
177
|
+
const d = this._data;
|
|
178
|
+
const cell = this._cellSize, gap = this._cellGap, r = this._cellRadius, step = this._step;
|
|
179
|
+
const levels = import_git_heatmap_core.DEFAULT_LEVELS;
|
|
180
|
+
const monthLabels = this._showMonths ? (0, import_git_heatmap_core.buildMonthLabels)(d.weeks) : [];
|
|
181
|
+
let monthRow = "";
|
|
182
|
+
if (this._showMonths) {
|
|
183
|
+
const cells = monthLabels.map(({ label, col }, idx) => {
|
|
184
|
+
const nextCol = monthLabels[idx + 1]?.col ?? d.weeks.length;
|
|
185
|
+
return `<div style="width:${(nextCol - col) * step}px;white-space:nowrap;font-size:0.9em;">${label}</div>`;
|
|
186
|
+
}).join("");
|
|
187
|
+
monthRow = `<div style="display:flex;margin-bottom:${gap}px;margin-left:${this._showDays ? 32 : 0}px;">${cells}</div>`;
|
|
188
|
+
}
|
|
189
|
+
let dayLabelCol = "";
|
|
190
|
+
if (this._showDays) {
|
|
191
|
+
const rows = Array.from(
|
|
192
|
+
{ length: 7 },
|
|
193
|
+
(_, dow) => `<div style="height:${cell}px;line-height:${cell}px;width:26px;text-align:right;padding-right:4px;font-size:0.9em;">${import_git_heatmap_core.DAY_LABELS[dow] ?? ""}</div>`
|
|
194
|
+
).join("");
|
|
195
|
+
dayLabelCol = `<div style="display:flex;flex-direction:column;gap:${gap}px;margin-right:6px;">${rows}</div>`;
|
|
196
|
+
}
|
|
197
|
+
const weekCols = d.weeks.map((week, _wi) => {
|
|
198
|
+
const cells = Array.from({ length: 7 }, (_, dow) => {
|
|
199
|
+
const day = week.days[dow];
|
|
200
|
+
if (!day?.date) return `<div style="width:${cell}px;height:${cell}px;flex-shrink:0;"></div>`;
|
|
201
|
+
const lvl = day.level;
|
|
202
|
+
const tip = day.count === 0 ? `No contributions on ${day.date}` : `${day.count} contribution${day.count > 1 ? "s" : ""} on ${day.date}`;
|
|
203
|
+
return `<div
|
|
204
|
+
class="ghm-cell"
|
|
205
|
+
style="width:${cell}px;height:${cell}px;border-radius:${r}px;background:var(--ghm-color-l${lvl});"
|
|
206
|
+
data-date="${day.date}"
|
|
207
|
+
data-count="${day.count}"
|
|
208
|
+
aria-label="${tip}"
|
|
209
|
+
role="button"
|
|
210
|
+
tabindex="0"
|
|
211
|
+
></div>`;
|
|
212
|
+
}).join("");
|
|
213
|
+
return `<div style="display:flex;flex-direction:column;gap:${gap}px;">${cells}</div>`;
|
|
214
|
+
}).join("");
|
|
215
|
+
let legend = "";
|
|
216
|
+
if (this._showLegend) {
|
|
217
|
+
const cells = levels.map(
|
|
218
|
+
(lvl, i) => `<div style="width:${cell}px;height:${cell}px;border-radius:${r}px;background:var(--ghm-color-l${i});" title="${lvl.label}"></div>`
|
|
219
|
+
).join("");
|
|
220
|
+
legend = `<div style="display:flex;align-items:center;justify-content:flex-end;margin-top:8px;">
|
|
221
|
+
<div style="display:flex;align-items:center;gap:6px;font-size:0.9em;">
|
|
222
|
+
<span>Less</span>${cells}<span>More</span>
|
|
223
|
+
</div>
|
|
224
|
+
</div>`;
|
|
225
|
+
}
|
|
226
|
+
const total = this._showTotal ? `<p style="margin-bottom:12px;color:var(--ghm-text);">${d.totalContributions.toLocaleString()} contributions in the last year</p>` : "";
|
|
227
|
+
this._shadow.innerHTML = `<style>${BASE_STYLES}</style>
|
|
228
|
+
<div aria-label="${this._label}" style="position:relative;">
|
|
229
|
+
${total}
|
|
230
|
+
<div class="ghm-scroll">
|
|
231
|
+
<div class="ghm-grid" style="min-width:${d.weeks.length * step}px;">
|
|
232
|
+
${monthRow}
|
|
233
|
+
<div style="display:flex;">
|
|
234
|
+
${dayLabelCol}
|
|
235
|
+
<div style="display:flex;gap:${gap}px;">${weekCols}</div>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
<div id="ghm-mobile-tip" style="display:none;margin-top:8px;font-size:0.9em;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.1);border-radius:4px;padding:6px 12px;"></div>
|
|
240
|
+
${legend}
|
|
241
|
+
<div class="ghm-tooltip" id="ghm-tt" style="display:none;"></div>
|
|
242
|
+
</div>`;
|
|
243
|
+
this._attachListeners();
|
|
244
|
+
const scrollEl = this._shadow.querySelector(".ghm-scroll");
|
|
245
|
+
if (scrollEl) scrollEl.scrollLeft = scrollEl.scrollWidth;
|
|
246
|
+
}
|
|
247
|
+
_attachListeners() {
|
|
248
|
+
const wrapper = this._shadow.firstElementChild;
|
|
249
|
+
const tt = this._shadow.getElementById("ghm-tt");
|
|
250
|
+
const mobileTip = this._shadow.getElementById("ghm-mobile-tip");
|
|
251
|
+
let activeTapDate = "";
|
|
252
|
+
this._shadow.querySelectorAll(".ghm-cell").forEach((el) => {
|
|
253
|
+
const cell = el;
|
|
254
|
+
const date = cell.dataset["date"] ?? "";
|
|
255
|
+
const count = parseInt(cell.dataset["count"] ?? "0", 10);
|
|
256
|
+
const tip = count === 0 ? `No contributions on ${date}` : `${count} contribution${count > 1 ? "s" : ""} on ${date}`;
|
|
257
|
+
cell.addEventListener("mouseenter", (e) => {
|
|
258
|
+
const r = cell.getBoundingClientRect();
|
|
259
|
+
const wr = wrapper.getBoundingClientRect();
|
|
260
|
+
tt.textContent = tip;
|
|
261
|
+
tt.style.left = `${r.left - wr.left + this._cellSize / 2}px`;
|
|
262
|
+
tt.style.top = `${r.top - wr.top - 6}px`;
|
|
263
|
+
tt.style.display = "block";
|
|
264
|
+
});
|
|
265
|
+
cell.addEventListener("mouseleave", () => {
|
|
266
|
+
tt.style.display = "none";
|
|
267
|
+
});
|
|
268
|
+
cell.addEventListener("click", () => {
|
|
269
|
+
if (activeTapDate === date) {
|
|
270
|
+
activeTapDate = "";
|
|
271
|
+
mobileTip.style.display = "none";
|
|
272
|
+
} else {
|
|
273
|
+
activeTapDate = date;
|
|
274
|
+
mobileTip.textContent = tip;
|
|
275
|
+
mobileTip.style.display = "block";
|
|
276
|
+
}
|
|
277
|
+
this.dispatchEvent(new CustomEvent("day-click", {
|
|
278
|
+
detail: { date, count },
|
|
279
|
+
bubbles: true,
|
|
280
|
+
composed: true
|
|
281
|
+
}));
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
if (typeof customElements !== "undefined" && !customElements.get("git-heatmap")) {
|
|
287
|
+
customElements.define("git-heatmap", GitHeatmapElement);
|
|
288
|
+
}
|
|
289
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
290
|
+
0 && (module.exports = {
|
|
291
|
+
GitHeatmapElement
|
|
292
|
+
});
|
|
293
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/git-heatmap-element.ts"],"sourcesContent":["export { GitHeatmapElement } from \"./git-heatmap-element\";\n","import {\n buildMonthLabels, CELL, GAP, STEP, DAY_LABELS,\n DEFAULT_LEVELS, DEFAULT_THEME, getLevel,\n} from \"@rsalianto/git-heatmap-core\";\nimport type { HeatmapData, HeatmapDay, LevelConfig } from \"@rsalianto/git-heatmap-core\";\n\nconst BASE_STYLES = `\n :host {\n --ghm-color-l0: rgba(255,255,255,0.08);\n --ghm-color-l1: #1c3d06;\n --ghm-color-l2: #3a7510;\n --ghm-color-l3: #6ab81e;\n --ghm-color-l4: #aafd35;\n --ghm-text: rgba(255,255,255,0.5);\n --ghm-tooltip-bg: #1c2128;\n --ghm-tooltip-border: rgba(255,255,255,0.1);\n --ghm-tooltip-text: rgba(255,255,255,0.75);\n --ghm-font: inherit;\n --ghm-fs: 11px;\n display: block;\n position: relative;\n width: 100%;\n font-size: var(--ghm-fs);\n font-family: var(--ghm-font);\n color: var(--ghm-text);\n user-select: none;\n box-sizing: border-box;\n }\n * { box-sizing: border-box; }\n .ghm-scroll { overflow-x: auto; overflow-y: visible; padding-bottom: 4px; }\n .ghm-grid { display: inline-flex; flex-direction: column; }\n .ghm-row { display: flex; }\n .ghm-col { display: flex; flex-direction: column; }\n .ghm-cell {\n border-radius: 2px;\n cursor: pointer;\n transition: opacity 0.15s;\n flex-shrink: 0;\n }\n .ghm-cell:hover { opacity: 0.7; }\n .ghm-tooltip {\n pointer-events: none;\n position: absolute;\n z-index: 50;\n transform: translate(-50%, -100%);\n background: var(--ghm-tooltip-bg);\n border: 1px solid var(--ghm-tooltip-border);\n color: var(--ghm-tooltip-text);\n font-size: 0.9em;\n border-radius: 4px;\n padding: 3px 8px;\n white-space: nowrap;\n }\n .ghm-skeleton { opacity: 0.4; }\n`;\n\nexport class GitHeatmapElement extends HTMLElement {\n static get observedAttributes() {\n return [\"api-url\", \"cell-size\", \"cell-gap\", \"cell-radius\", \"show-total\", \"show-legend\", \"show-month-labels\", \"show-day-labels\", \"label\"];\n }\n\n private _data: HeatmapData | null = null;\n private _fetchData: (() => Promise<HeatmapData>) | null = null;\n private _shadow: ShadowRoot;\n private _tooltip: HTMLDivElement | null = null;\n\n set data(value: HeatmapData) { this._data = value; this._render(); }\n get data(): HeatmapData | null { return this._data; }\n\n set fetchData(fn: () => Promise<HeatmapData>) { this._fetchData = fn; this._load(); }\n\n constructor() {\n super();\n this._shadow = this.attachShadow({ mode: \"open\" });\n }\n\n connectedCallback() {\n const apiUrl = this.getAttribute(\"api-url\");\n if (apiUrl && !this._data) {\n this._load();\n } else if (this._data) {\n this._render();\n } else {\n this._renderSkeleton();\n }\n }\n\n attributeChangedCallback(name: string, _old: string | null, _val: string | null) {\n if (name === \"api-url\") { this._load(); return; }\n if (this._data) this._render();\n }\n\n private get _cellSize() { return parseInt(this.getAttribute(\"cell-size\") ?? String(CELL), 10); }\n private get _cellGap() { return parseInt(this.getAttribute(\"cell-gap\") ?? String(GAP), 10); }\n private get _cellRadius(){ return parseInt(this.getAttribute(\"cell-radius\") ?? \"2\", 10); }\n private get _step() { return this._cellSize + this._cellGap; }\n private get _showTotal() { return this.getAttribute(\"show-total\") !== \"false\"; }\n private get _showLegend(){ return this.getAttribute(\"show-legend\") !== \"false\"; }\n private get _showMonths(){ return this.getAttribute(\"show-month-labels\") !== \"false\"; }\n private get _showDays() { return this.getAttribute(\"show-day-labels\") !== \"false\"; }\n private get _label() { return this.getAttribute(\"label\") ?? \"Contribution heatmap\"; }\n\n private async _load() {\n const apiUrl = this.getAttribute(\"api-url\");\n const resolver = this._fetchData\n ?? (apiUrl ? () => fetch(apiUrl).then((r) => r.json()) : null);\n if (!resolver) return;\n\n this._renderSkeleton();\n try {\n this._data = await resolver() as HeatmapData;\n this._render();\n } catch {\n this._renderError();\n }\n }\n\n private _renderSkeleton() {\n const cell = this._cellSize, gap = this._cellGap, step = this._step;\n let cols = \"\";\n for (let c = 0; c < 53; c++) {\n let cells = \"\";\n for (let r = 0; r < 7; r++) {\n cells += `<div style=\"width:${cell}px;height:${cell}px;border-radius:${this._cellRadius}px;background:var(--ghm-color-l0);flex-shrink:0;\"></div>`;\n }\n cols += `<div style=\"display:flex;flex-direction:column;gap:${gap}px;\">${cells}</div>`;\n }\n this._shadow.innerHTML = `<style>${BASE_STYLES}</style>\n <div aria-label=\"${this._label}\" style=\"opacity:0.4;\">\n <div style=\"display:flex;gap:${gap}px;\">${cols}</div>\n </div>`;\n }\n\n private _renderError() {\n this._shadow.innerHTML = `<style>${BASE_STYLES}</style>\n <p style=\"opacity:0.6;padding:1rem 0;\">Could not load contribution data.</p>`;\n }\n\n private _render() {\n if (!this._data) return;\n const d = this._data;\n const cell = this._cellSize, gap = this._cellGap, r = this._cellRadius, step = this._step;\n const levels: LevelConfig[] = DEFAULT_LEVELS;\n\n const monthLabels = this._showMonths ? buildMonthLabels(d.weeks) : [];\n\n let monthRow = \"\";\n if (this._showMonths) {\n const cells = monthLabels.map(({ label, col }, idx) => {\n const nextCol = monthLabels[idx + 1]?.col ?? d.weeks.length;\n return `<div style=\"width:${(nextCol - col) * step}px;white-space:nowrap;font-size:0.9em;\">${label}</div>`;\n }).join(\"\");\n monthRow = `<div style=\"display:flex;margin-bottom:${gap}px;margin-left:${this._showDays ? 32 : 0}px;\">${cells}</div>`;\n }\n\n let dayLabelCol = \"\";\n if (this._showDays) {\n const rows = Array.from({ length: 7 }, (_, dow) =>\n `<div style=\"height:${cell}px;line-height:${cell}px;width:26px;text-align:right;padding-right:4px;font-size:0.9em;\">${DAY_LABELS[dow] ?? \"\"}</div>`\n ).join(\"\");\n dayLabelCol = `<div style=\"display:flex;flex-direction:column;gap:${gap}px;margin-right:6px;\">${rows}</div>`;\n }\n\n const weekCols = d.weeks.map((week, _wi) => {\n const cells = Array.from({ length: 7 }, (_, dow) => {\n const day = week.days[dow];\n if (!day?.date) return `<div style=\"width:${cell}px;height:${cell}px;flex-shrink:0;\"></div>`;\n const lvl = day.level;\n const tip = day.count === 0\n ? `No contributions on ${day.date}`\n : `${day.count} contribution${day.count > 1 ? \"s\" : \"\"} on ${day.date}`;\n return `<div\n class=\"ghm-cell\"\n style=\"width:${cell}px;height:${cell}px;border-radius:${r}px;background:var(--ghm-color-l${lvl});\"\n data-date=\"${day.date}\"\n data-count=\"${day.count}\"\n aria-label=\"${tip}\"\n role=\"button\"\n tabindex=\"0\"\n ></div>`;\n }).join(\"\");\n return `<div style=\"display:flex;flex-direction:column;gap:${gap}px;\">${cells}</div>`;\n }).join(\"\");\n\n let legend = \"\";\n if (this._showLegend) {\n const cells = levels.map((lvl, i) =>\n `<div style=\"width:${cell}px;height:${cell}px;border-radius:${r}px;background:var(--ghm-color-l${i});\" title=\"${lvl.label}\"></div>`\n ).join(\"\");\n legend = `<div style=\"display:flex;align-items:center;justify-content:flex-end;margin-top:8px;\">\n <div style=\"display:flex;align-items:center;gap:6px;font-size:0.9em;\">\n <span>Less</span>${cells}<span>More</span>\n </div>\n </div>`;\n }\n\n const total = this._showTotal\n ? `<p style=\"margin-bottom:12px;color:var(--ghm-text);\">${d.totalContributions.toLocaleString()} contributions in the last year</p>`\n : \"\";\n\n this._shadow.innerHTML = `<style>${BASE_STYLES}</style>\n <div aria-label=\"${this._label}\" style=\"position:relative;\">\n ${total}\n <div class=\"ghm-scroll\">\n <div class=\"ghm-grid\" style=\"min-width:${d.weeks.length * step}px;\">\n ${monthRow}\n <div style=\"display:flex;\">\n ${dayLabelCol}\n <div style=\"display:flex;gap:${gap}px;\">${weekCols}</div>\n </div>\n </div>\n </div>\n <div id=\"ghm-mobile-tip\" style=\"display:none;margin-top:8px;font-size:0.9em;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.1);border-radius:4px;padding:6px 12px;\"></div>\n ${legend}\n <div class=\"ghm-tooltip\" id=\"ghm-tt\" style=\"display:none;\"></div>\n </div>`;\n\n this._attachListeners();\n\n // auto-scroll to end\n const scrollEl = this._shadow.querySelector(\".ghm-scroll\") as HTMLElement | null;\n if (scrollEl) scrollEl.scrollLeft = scrollEl.scrollWidth;\n }\n\n private _attachListeners() {\n const wrapper = this._shadow.firstElementChild as HTMLElement;\n const tt = this._shadow.getElementById(\"ghm-tt\") as HTMLDivElement;\n const mobileTip = this._shadow.getElementById(\"ghm-mobile-tip\") as HTMLDivElement;\n let activeTapDate = \"\";\n\n this._shadow.querySelectorAll(\".ghm-cell\").forEach((el) => {\n const cell = el as HTMLElement;\n const date = cell.dataset[\"date\"] ?? \"\";\n const count = parseInt(cell.dataset[\"count\"] ?? \"0\", 10);\n const tip = count === 0\n ? `No contributions on ${date}`\n : `${count} contribution${count > 1 ? \"s\" : \"\"} on ${date}`;\n\n cell.addEventListener(\"mouseenter\", (e) => {\n const r = cell.getBoundingClientRect();\n const wr = wrapper.getBoundingClientRect();\n tt.textContent = tip;\n tt.style.left = `${r.left - wr.left + this._cellSize / 2}px`;\n tt.style.top = `${r.top - wr.top - 6}px`;\n tt.style.display = \"block\";\n });\n cell.addEventListener(\"mouseleave\", () => { tt.style.display = \"none\"; });\n cell.addEventListener(\"click\", () => {\n if (activeTapDate === date) {\n activeTapDate = \"\";\n mobileTip.style.display = \"none\";\n } else {\n activeTapDate = date;\n mobileTip.textContent = tip;\n mobileTip.style.display = \"block\";\n }\n this.dispatchEvent(new CustomEvent(\"day-click\", {\n detail: { date, count },\n bubbles: true,\n composed: true,\n }));\n });\n });\n }\n}\n\nif (typeof customElements !== \"undefined\" && !customElements.get(\"git-heatmap\")) {\n customElements.define(\"git-heatmap\", GitHeatmapElement);\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
// src/git-heatmap-element.ts
|
|
2
|
+
import {
|
|
3
|
+
buildMonthLabels,
|
|
4
|
+
CELL,
|
|
5
|
+
GAP,
|
|
6
|
+
DAY_LABELS,
|
|
7
|
+
DEFAULT_LEVELS
|
|
8
|
+
} from "@rsalianto/git-heatmap-core";
|
|
9
|
+
var BASE_STYLES = `
|
|
10
|
+
:host {
|
|
11
|
+
--ghm-color-l0: rgba(255,255,255,0.08);
|
|
12
|
+
--ghm-color-l1: #1c3d06;
|
|
13
|
+
--ghm-color-l2: #3a7510;
|
|
14
|
+
--ghm-color-l3: #6ab81e;
|
|
15
|
+
--ghm-color-l4: #aafd35;
|
|
16
|
+
--ghm-text: rgba(255,255,255,0.5);
|
|
17
|
+
--ghm-tooltip-bg: #1c2128;
|
|
18
|
+
--ghm-tooltip-border: rgba(255,255,255,0.1);
|
|
19
|
+
--ghm-tooltip-text: rgba(255,255,255,0.75);
|
|
20
|
+
--ghm-font: inherit;
|
|
21
|
+
--ghm-fs: 11px;
|
|
22
|
+
display: block;
|
|
23
|
+
position: relative;
|
|
24
|
+
width: 100%;
|
|
25
|
+
font-size: var(--ghm-fs);
|
|
26
|
+
font-family: var(--ghm-font);
|
|
27
|
+
color: var(--ghm-text);
|
|
28
|
+
user-select: none;
|
|
29
|
+
box-sizing: border-box;
|
|
30
|
+
}
|
|
31
|
+
* { box-sizing: border-box; }
|
|
32
|
+
.ghm-scroll { overflow-x: auto; overflow-y: visible; padding-bottom: 4px; }
|
|
33
|
+
.ghm-grid { display: inline-flex; flex-direction: column; }
|
|
34
|
+
.ghm-row { display: flex; }
|
|
35
|
+
.ghm-col { display: flex; flex-direction: column; }
|
|
36
|
+
.ghm-cell {
|
|
37
|
+
border-radius: 2px;
|
|
38
|
+
cursor: pointer;
|
|
39
|
+
transition: opacity 0.15s;
|
|
40
|
+
flex-shrink: 0;
|
|
41
|
+
}
|
|
42
|
+
.ghm-cell:hover { opacity: 0.7; }
|
|
43
|
+
.ghm-tooltip {
|
|
44
|
+
pointer-events: none;
|
|
45
|
+
position: absolute;
|
|
46
|
+
z-index: 50;
|
|
47
|
+
transform: translate(-50%, -100%);
|
|
48
|
+
background: var(--ghm-tooltip-bg);
|
|
49
|
+
border: 1px solid var(--ghm-tooltip-border);
|
|
50
|
+
color: var(--ghm-tooltip-text);
|
|
51
|
+
font-size: 0.9em;
|
|
52
|
+
border-radius: 4px;
|
|
53
|
+
padding: 3px 8px;
|
|
54
|
+
white-space: nowrap;
|
|
55
|
+
}
|
|
56
|
+
.ghm-skeleton { opacity: 0.4; }
|
|
57
|
+
`;
|
|
58
|
+
var GitHeatmapElement = class extends HTMLElement {
|
|
59
|
+
constructor() {
|
|
60
|
+
super();
|
|
61
|
+
this._data = null;
|
|
62
|
+
this._fetchData = null;
|
|
63
|
+
this._tooltip = null;
|
|
64
|
+
this._shadow = this.attachShadow({ mode: "open" });
|
|
65
|
+
}
|
|
66
|
+
static get observedAttributes() {
|
|
67
|
+
return ["api-url", "cell-size", "cell-gap", "cell-radius", "show-total", "show-legend", "show-month-labels", "show-day-labels", "label"];
|
|
68
|
+
}
|
|
69
|
+
set data(value) {
|
|
70
|
+
this._data = value;
|
|
71
|
+
this._render();
|
|
72
|
+
}
|
|
73
|
+
get data() {
|
|
74
|
+
return this._data;
|
|
75
|
+
}
|
|
76
|
+
set fetchData(fn) {
|
|
77
|
+
this._fetchData = fn;
|
|
78
|
+
this._load();
|
|
79
|
+
}
|
|
80
|
+
connectedCallback() {
|
|
81
|
+
const apiUrl = this.getAttribute("api-url");
|
|
82
|
+
if (apiUrl && !this._data) {
|
|
83
|
+
this._load();
|
|
84
|
+
} else if (this._data) {
|
|
85
|
+
this._render();
|
|
86
|
+
} else {
|
|
87
|
+
this._renderSkeleton();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
attributeChangedCallback(name, _old, _val) {
|
|
91
|
+
if (name === "api-url") {
|
|
92
|
+
this._load();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (this._data) this._render();
|
|
96
|
+
}
|
|
97
|
+
get _cellSize() {
|
|
98
|
+
return parseInt(this.getAttribute("cell-size") ?? String(CELL), 10);
|
|
99
|
+
}
|
|
100
|
+
get _cellGap() {
|
|
101
|
+
return parseInt(this.getAttribute("cell-gap") ?? String(GAP), 10);
|
|
102
|
+
}
|
|
103
|
+
get _cellRadius() {
|
|
104
|
+
return parseInt(this.getAttribute("cell-radius") ?? "2", 10);
|
|
105
|
+
}
|
|
106
|
+
get _step() {
|
|
107
|
+
return this._cellSize + this._cellGap;
|
|
108
|
+
}
|
|
109
|
+
get _showTotal() {
|
|
110
|
+
return this.getAttribute("show-total") !== "false";
|
|
111
|
+
}
|
|
112
|
+
get _showLegend() {
|
|
113
|
+
return this.getAttribute("show-legend") !== "false";
|
|
114
|
+
}
|
|
115
|
+
get _showMonths() {
|
|
116
|
+
return this.getAttribute("show-month-labels") !== "false";
|
|
117
|
+
}
|
|
118
|
+
get _showDays() {
|
|
119
|
+
return this.getAttribute("show-day-labels") !== "false";
|
|
120
|
+
}
|
|
121
|
+
get _label() {
|
|
122
|
+
return this.getAttribute("label") ?? "Contribution heatmap";
|
|
123
|
+
}
|
|
124
|
+
async _load() {
|
|
125
|
+
const apiUrl = this.getAttribute("api-url");
|
|
126
|
+
const resolver = this._fetchData ?? (apiUrl ? () => fetch(apiUrl).then((r) => r.json()) : null);
|
|
127
|
+
if (!resolver) return;
|
|
128
|
+
this._renderSkeleton();
|
|
129
|
+
try {
|
|
130
|
+
this._data = await resolver();
|
|
131
|
+
this._render();
|
|
132
|
+
} catch {
|
|
133
|
+
this._renderError();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
_renderSkeleton() {
|
|
137
|
+
const cell = this._cellSize, gap = this._cellGap, step = this._step;
|
|
138
|
+
let cols = "";
|
|
139
|
+
for (let c = 0; c < 53; c++) {
|
|
140
|
+
let cells = "";
|
|
141
|
+
for (let r = 0; r < 7; r++) {
|
|
142
|
+
cells += `<div style="width:${cell}px;height:${cell}px;border-radius:${this._cellRadius}px;background:var(--ghm-color-l0);flex-shrink:0;"></div>`;
|
|
143
|
+
}
|
|
144
|
+
cols += `<div style="display:flex;flex-direction:column;gap:${gap}px;">${cells}</div>`;
|
|
145
|
+
}
|
|
146
|
+
this._shadow.innerHTML = `<style>${BASE_STYLES}</style>
|
|
147
|
+
<div aria-label="${this._label}" style="opacity:0.4;">
|
|
148
|
+
<div style="display:flex;gap:${gap}px;">${cols}</div>
|
|
149
|
+
</div>`;
|
|
150
|
+
}
|
|
151
|
+
_renderError() {
|
|
152
|
+
this._shadow.innerHTML = `<style>${BASE_STYLES}</style>
|
|
153
|
+
<p style="opacity:0.6;padding:1rem 0;">Could not load contribution data.</p>`;
|
|
154
|
+
}
|
|
155
|
+
_render() {
|
|
156
|
+
if (!this._data) return;
|
|
157
|
+
const d = this._data;
|
|
158
|
+
const cell = this._cellSize, gap = this._cellGap, r = this._cellRadius, step = this._step;
|
|
159
|
+
const levels = DEFAULT_LEVELS;
|
|
160
|
+
const monthLabels = this._showMonths ? buildMonthLabels(d.weeks) : [];
|
|
161
|
+
let monthRow = "";
|
|
162
|
+
if (this._showMonths) {
|
|
163
|
+
const cells = monthLabels.map(({ label, col }, idx) => {
|
|
164
|
+
const nextCol = monthLabels[idx + 1]?.col ?? d.weeks.length;
|
|
165
|
+
return `<div style="width:${(nextCol - col) * step}px;white-space:nowrap;font-size:0.9em;">${label}</div>`;
|
|
166
|
+
}).join("");
|
|
167
|
+
monthRow = `<div style="display:flex;margin-bottom:${gap}px;margin-left:${this._showDays ? 32 : 0}px;">${cells}</div>`;
|
|
168
|
+
}
|
|
169
|
+
let dayLabelCol = "";
|
|
170
|
+
if (this._showDays) {
|
|
171
|
+
const rows = Array.from(
|
|
172
|
+
{ length: 7 },
|
|
173
|
+
(_, dow) => `<div style="height:${cell}px;line-height:${cell}px;width:26px;text-align:right;padding-right:4px;font-size:0.9em;">${DAY_LABELS[dow] ?? ""}</div>`
|
|
174
|
+
).join("");
|
|
175
|
+
dayLabelCol = `<div style="display:flex;flex-direction:column;gap:${gap}px;margin-right:6px;">${rows}</div>`;
|
|
176
|
+
}
|
|
177
|
+
const weekCols = d.weeks.map((week, _wi) => {
|
|
178
|
+
const cells = Array.from({ length: 7 }, (_, dow) => {
|
|
179
|
+
const day = week.days[dow];
|
|
180
|
+
if (!day?.date) return `<div style="width:${cell}px;height:${cell}px;flex-shrink:0;"></div>`;
|
|
181
|
+
const lvl = day.level;
|
|
182
|
+
const tip = day.count === 0 ? `No contributions on ${day.date}` : `${day.count} contribution${day.count > 1 ? "s" : ""} on ${day.date}`;
|
|
183
|
+
return `<div
|
|
184
|
+
class="ghm-cell"
|
|
185
|
+
style="width:${cell}px;height:${cell}px;border-radius:${r}px;background:var(--ghm-color-l${lvl});"
|
|
186
|
+
data-date="${day.date}"
|
|
187
|
+
data-count="${day.count}"
|
|
188
|
+
aria-label="${tip}"
|
|
189
|
+
role="button"
|
|
190
|
+
tabindex="0"
|
|
191
|
+
></div>`;
|
|
192
|
+
}).join("");
|
|
193
|
+
return `<div style="display:flex;flex-direction:column;gap:${gap}px;">${cells}</div>`;
|
|
194
|
+
}).join("");
|
|
195
|
+
let legend = "";
|
|
196
|
+
if (this._showLegend) {
|
|
197
|
+
const cells = levels.map(
|
|
198
|
+
(lvl, i) => `<div style="width:${cell}px;height:${cell}px;border-radius:${r}px;background:var(--ghm-color-l${i});" title="${lvl.label}"></div>`
|
|
199
|
+
).join("");
|
|
200
|
+
legend = `<div style="display:flex;align-items:center;justify-content:flex-end;margin-top:8px;">
|
|
201
|
+
<div style="display:flex;align-items:center;gap:6px;font-size:0.9em;">
|
|
202
|
+
<span>Less</span>${cells}<span>More</span>
|
|
203
|
+
</div>
|
|
204
|
+
</div>`;
|
|
205
|
+
}
|
|
206
|
+
const total = this._showTotal ? `<p style="margin-bottom:12px;color:var(--ghm-text);">${d.totalContributions.toLocaleString()} contributions in the last year</p>` : "";
|
|
207
|
+
this._shadow.innerHTML = `<style>${BASE_STYLES}</style>
|
|
208
|
+
<div aria-label="${this._label}" style="position:relative;">
|
|
209
|
+
${total}
|
|
210
|
+
<div class="ghm-scroll">
|
|
211
|
+
<div class="ghm-grid" style="min-width:${d.weeks.length * step}px;">
|
|
212
|
+
${monthRow}
|
|
213
|
+
<div style="display:flex;">
|
|
214
|
+
${dayLabelCol}
|
|
215
|
+
<div style="display:flex;gap:${gap}px;">${weekCols}</div>
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
<div id="ghm-mobile-tip" style="display:none;margin-top:8px;font-size:0.9em;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.1);border-radius:4px;padding:6px 12px;"></div>
|
|
220
|
+
${legend}
|
|
221
|
+
<div class="ghm-tooltip" id="ghm-tt" style="display:none;"></div>
|
|
222
|
+
</div>`;
|
|
223
|
+
this._attachListeners();
|
|
224
|
+
const scrollEl = this._shadow.querySelector(".ghm-scroll");
|
|
225
|
+
if (scrollEl) scrollEl.scrollLeft = scrollEl.scrollWidth;
|
|
226
|
+
}
|
|
227
|
+
_attachListeners() {
|
|
228
|
+
const wrapper = this._shadow.firstElementChild;
|
|
229
|
+
const tt = this._shadow.getElementById("ghm-tt");
|
|
230
|
+
const mobileTip = this._shadow.getElementById("ghm-mobile-tip");
|
|
231
|
+
let activeTapDate = "";
|
|
232
|
+
this._shadow.querySelectorAll(".ghm-cell").forEach((el) => {
|
|
233
|
+
const cell = el;
|
|
234
|
+
const date = cell.dataset["date"] ?? "";
|
|
235
|
+
const count = parseInt(cell.dataset["count"] ?? "0", 10);
|
|
236
|
+
const tip = count === 0 ? `No contributions on ${date}` : `${count} contribution${count > 1 ? "s" : ""} on ${date}`;
|
|
237
|
+
cell.addEventListener("mouseenter", (e) => {
|
|
238
|
+
const r = cell.getBoundingClientRect();
|
|
239
|
+
const wr = wrapper.getBoundingClientRect();
|
|
240
|
+
tt.textContent = tip;
|
|
241
|
+
tt.style.left = `${r.left - wr.left + this._cellSize / 2}px`;
|
|
242
|
+
tt.style.top = `${r.top - wr.top - 6}px`;
|
|
243
|
+
tt.style.display = "block";
|
|
244
|
+
});
|
|
245
|
+
cell.addEventListener("mouseleave", () => {
|
|
246
|
+
tt.style.display = "none";
|
|
247
|
+
});
|
|
248
|
+
cell.addEventListener("click", () => {
|
|
249
|
+
if (activeTapDate === date) {
|
|
250
|
+
activeTapDate = "";
|
|
251
|
+
mobileTip.style.display = "none";
|
|
252
|
+
} else {
|
|
253
|
+
activeTapDate = date;
|
|
254
|
+
mobileTip.textContent = tip;
|
|
255
|
+
mobileTip.style.display = "block";
|
|
256
|
+
}
|
|
257
|
+
this.dispatchEvent(new CustomEvent("day-click", {
|
|
258
|
+
detail: { date, count },
|
|
259
|
+
bubbles: true,
|
|
260
|
+
composed: true
|
|
261
|
+
}));
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
if (typeof customElements !== "undefined" && !customElements.get("git-heatmap")) {
|
|
267
|
+
customElements.define("git-heatmap", GitHeatmapElement);
|
|
268
|
+
}
|
|
269
|
+
export {
|
|
270
|
+
GitHeatmapElement
|
|
271
|
+
};
|
|
272
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/git-heatmap-element.ts"],"sourcesContent":["import {\n buildMonthLabels, CELL, GAP, STEP, DAY_LABELS,\n DEFAULT_LEVELS, DEFAULT_THEME, getLevel,\n} from \"@rsalianto/git-heatmap-core\";\nimport type { HeatmapData, HeatmapDay, LevelConfig } from \"@rsalianto/git-heatmap-core\";\n\nconst BASE_STYLES = `\n :host {\n --ghm-color-l0: rgba(255,255,255,0.08);\n --ghm-color-l1: #1c3d06;\n --ghm-color-l2: #3a7510;\n --ghm-color-l3: #6ab81e;\n --ghm-color-l4: #aafd35;\n --ghm-text: rgba(255,255,255,0.5);\n --ghm-tooltip-bg: #1c2128;\n --ghm-tooltip-border: rgba(255,255,255,0.1);\n --ghm-tooltip-text: rgba(255,255,255,0.75);\n --ghm-font: inherit;\n --ghm-fs: 11px;\n display: block;\n position: relative;\n width: 100%;\n font-size: var(--ghm-fs);\n font-family: var(--ghm-font);\n color: var(--ghm-text);\n user-select: none;\n box-sizing: border-box;\n }\n * { box-sizing: border-box; }\n .ghm-scroll { overflow-x: auto; overflow-y: visible; padding-bottom: 4px; }\n .ghm-grid { display: inline-flex; flex-direction: column; }\n .ghm-row { display: flex; }\n .ghm-col { display: flex; flex-direction: column; }\n .ghm-cell {\n border-radius: 2px;\n cursor: pointer;\n transition: opacity 0.15s;\n flex-shrink: 0;\n }\n .ghm-cell:hover { opacity: 0.7; }\n .ghm-tooltip {\n pointer-events: none;\n position: absolute;\n z-index: 50;\n transform: translate(-50%, -100%);\n background: var(--ghm-tooltip-bg);\n border: 1px solid var(--ghm-tooltip-border);\n color: var(--ghm-tooltip-text);\n font-size: 0.9em;\n border-radius: 4px;\n padding: 3px 8px;\n white-space: nowrap;\n }\n .ghm-skeleton { opacity: 0.4; }\n`;\n\nexport class GitHeatmapElement extends HTMLElement {\n static get observedAttributes() {\n return [\"api-url\", \"cell-size\", \"cell-gap\", \"cell-radius\", \"show-total\", \"show-legend\", \"show-month-labels\", \"show-day-labels\", \"label\"];\n }\n\n private _data: HeatmapData | null = null;\n private _fetchData: (() => Promise<HeatmapData>) | null = null;\n private _shadow: ShadowRoot;\n private _tooltip: HTMLDivElement | null = null;\n\n set data(value: HeatmapData) { this._data = value; this._render(); }\n get data(): HeatmapData | null { return this._data; }\n\n set fetchData(fn: () => Promise<HeatmapData>) { this._fetchData = fn; this._load(); }\n\n constructor() {\n super();\n this._shadow = this.attachShadow({ mode: \"open\" });\n }\n\n connectedCallback() {\n const apiUrl = this.getAttribute(\"api-url\");\n if (apiUrl && !this._data) {\n this._load();\n } else if (this._data) {\n this._render();\n } else {\n this._renderSkeleton();\n }\n }\n\n attributeChangedCallback(name: string, _old: string | null, _val: string | null) {\n if (name === \"api-url\") { this._load(); return; }\n if (this._data) this._render();\n }\n\n private get _cellSize() { return parseInt(this.getAttribute(\"cell-size\") ?? String(CELL), 10); }\n private get _cellGap() { return parseInt(this.getAttribute(\"cell-gap\") ?? String(GAP), 10); }\n private get _cellRadius(){ return parseInt(this.getAttribute(\"cell-radius\") ?? \"2\", 10); }\n private get _step() { return this._cellSize + this._cellGap; }\n private get _showTotal() { return this.getAttribute(\"show-total\") !== \"false\"; }\n private get _showLegend(){ return this.getAttribute(\"show-legend\") !== \"false\"; }\n private get _showMonths(){ return this.getAttribute(\"show-month-labels\") !== \"false\"; }\n private get _showDays() { return this.getAttribute(\"show-day-labels\") !== \"false\"; }\n private get _label() { return this.getAttribute(\"label\") ?? \"Contribution heatmap\"; }\n\n private async _load() {\n const apiUrl = this.getAttribute(\"api-url\");\n const resolver = this._fetchData\n ?? (apiUrl ? () => fetch(apiUrl).then((r) => r.json()) : null);\n if (!resolver) return;\n\n this._renderSkeleton();\n try {\n this._data = await resolver() as HeatmapData;\n this._render();\n } catch {\n this._renderError();\n }\n }\n\n private _renderSkeleton() {\n const cell = this._cellSize, gap = this._cellGap, step = this._step;\n let cols = \"\";\n for (let c = 0; c < 53; c++) {\n let cells = \"\";\n for (let r = 0; r < 7; r++) {\n cells += `<div style=\"width:${cell}px;height:${cell}px;border-radius:${this._cellRadius}px;background:var(--ghm-color-l0);flex-shrink:0;\"></div>`;\n }\n cols += `<div style=\"display:flex;flex-direction:column;gap:${gap}px;\">${cells}</div>`;\n }\n this._shadow.innerHTML = `<style>${BASE_STYLES}</style>\n <div aria-label=\"${this._label}\" style=\"opacity:0.4;\">\n <div style=\"display:flex;gap:${gap}px;\">${cols}</div>\n </div>`;\n }\n\n private _renderError() {\n this._shadow.innerHTML = `<style>${BASE_STYLES}</style>\n <p style=\"opacity:0.6;padding:1rem 0;\">Could not load contribution data.</p>`;\n }\n\n private _render() {\n if (!this._data) return;\n const d = this._data;\n const cell = this._cellSize, gap = this._cellGap, r = this._cellRadius, step = this._step;\n const levels: LevelConfig[] = DEFAULT_LEVELS;\n\n const monthLabels = this._showMonths ? buildMonthLabels(d.weeks) : [];\n\n let monthRow = \"\";\n if (this._showMonths) {\n const cells = monthLabels.map(({ label, col }, idx) => {\n const nextCol = monthLabels[idx + 1]?.col ?? d.weeks.length;\n return `<div style=\"width:${(nextCol - col) * step}px;white-space:nowrap;font-size:0.9em;\">${label}</div>`;\n }).join(\"\");\n monthRow = `<div style=\"display:flex;margin-bottom:${gap}px;margin-left:${this._showDays ? 32 : 0}px;\">${cells}</div>`;\n }\n\n let dayLabelCol = \"\";\n if (this._showDays) {\n const rows = Array.from({ length: 7 }, (_, dow) =>\n `<div style=\"height:${cell}px;line-height:${cell}px;width:26px;text-align:right;padding-right:4px;font-size:0.9em;\">${DAY_LABELS[dow] ?? \"\"}</div>`\n ).join(\"\");\n dayLabelCol = `<div style=\"display:flex;flex-direction:column;gap:${gap}px;margin-right:6px;\">${rows}</div>`;\n }\n\n const weekCols = d.weeks.map((week, _wi) => {\n const cells = Array.from({ length: 7 }, (_, dow) => {\n const day = week.days[dow];\n if (!day?.date) return `<div style=\"width:${cell}px;height:${cell}px;flex-shrink:0;\"></div>`;\n const lvl = day.level;\n const tip = day.count === 0\n ? `No contributions on ${day.date}`\n : `${day.count} contribution${day.count > 1 ? \"s\" : \"\"} on ${day.date}`;\n return `<div\n class=\"ghm-cell\"\n style=\"width:${cell}px;height:${cell}px;border-radius:${r}px;background:var(--ghm-color-l${lvl});\"\n data-date=\"${day.date}\"\n data-count=\"${day.count}\"\n aria-label=\"${tip}\"\n role=\"button\"\n tabindex=\"0\"\n ></div>`;\n }).join(\"\");\n return `<div style=\"display:flex;flex-direction:column;gap:${gap}px;\">${cells}</div>`;\n }).join(\"\");\n\n let legend = \"\";\n if (this._showLegend) {\n const cells = levels.map((lvl, i) =>\n `<div style=\"width:${cell}px;height:${cell}px;border-radius:${r}px;background:var(--ghm-color-l${i});\" title=\"${lvl.label}\"></div>`\n ).join(\"\");\n legend = `<div style=\"display:flex;align-items:center;justify-content:flex-end;margin-top:8px;\">\n <div style=\"display:flex;align-items:center;gap:6px;font-size:0.9em;\">\n <span>Less</span>${cells}<span>More</span>\n </div>\n </div>`;\n }\n\n const total = this._showTotal\n ? `<p style=\"margin-bottom:12px;color:var(--ghm-text);\">${d.totalContributions.toLocaleString()} contributions in the last year</p>`\n : \"\";\n\n this._shadow.innerHTML = `<style>${BASE_STYLES}</style>\n <div aria-label=\"${this._label}\" style=\"position:relative;\">\n ${total}\n <div class=\"ghm-scroll\">\n <div class=\"ghm-grid\" style=\"min-width:${d.weeks.length * step}px;\">\n ${monthRow}\n <div style=\"display:flex;\">\n ${dayLabelCol}\n <div style=\"display:flex;gap:${gap}px;\">${weekCols}</div>\n </div>\n </div>\n </div>\n <div id=\"ghm-mobile-tip\" style=\"display:none;margin-top:8px;font-size:0.9em;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.1);border-radius:4px;padding:6px 12px;\"></div>\n ${legend}\n <div class=\"ghm-tooltip\" id=\"ghm-tt\" style=\"display:none;\"></div>\n </div>`;\n\n this._attachListeners();\n\n // auto-scroll to end\n const scrollEl = this._shadow.querySelector(\".ghm-scroll\") as HTMLElement | null;\n if (scrollEl) scrollEl.scrollLeft = scrollEl.scrollWidth;\n }\n\n private _attachListeners() {\n const wrapper = this._shadow.firstElementChild as HTMLElement;\n const tt = this._shadow.getElementById(\"ghm-tt\") as HTMLDivElement;\n const mobileTip = this._shadow.getElementById(\"ghm-mobile-tip\") as HTMLDivElement;\n let activeTapDate = \"\";\n\n this._shadow.querySelectorAll(\".ghm-cell\").forEach((el) => {\n const cell = el as HTMLElement;\n const date = cell.dataset[\"date\"] ?? \"\";\n const count = parseInt(cell.dataset[\"count\"] ?? \"0\", 10);\n const tip = count === 0\n ? `No contributions on ${date}`\n : `${count} contribution${count > 1 ? \"s\" : \"\"} on ${date}`;\n\n cell.addEventListener(\"mouseenter\", (e) => {\n const r = cell.getBoundingClientRect();\n const wr = wrapper.getBoundingClientRect();\n tt.textContent = tip;\n tt.style.left = `${r.left - wr.left + this._cellSize / 2}px`;\n tt.style.top = `${r.top - wr.top - 6}px`;\n tt.style.display = \"block\";\n });\n cell.addEventListener(\"mouseleave\", () => { tt.style.display = \"none\"; });\n cell.addEventListener(\"click\", () => {\n if (activeTapDate === date) {\n activeTapDate = \"\";\n mobileTip.style.display = \"none\";\n } else {\n activeTapDate = date;\n mobileTip.textContent = tip;\n mobileTip.style.display = \"block\";\n }\n this.dispatchEvent(new CustomEvent(\"day-click\", {\n detail: { date, count },\n bubbles: true,\n composed: true,\n }));\n });\n });\n }\n}\n\nif (typeof customElements !== \"undefined\" && !customElements.get(\"git-heatmap\")) {\n customElements.define(\"git-heatmap\", GitHeatmapElement);\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rsalianto/git-heatmap-vanilla",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Vanilla JS Web Component for git contribution heatmap",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.mjs",
|
|
13
|
+
"require": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": ["dist", "README.md"],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsup",
|
|
19
|
+
"dev": "tsup --watch",
|
|
20
|
+
"test": "vitest run",
|
|
21
|
+
"clean": "rm -rf dist"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@rsalianto/git-heatmap-core": "*"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"tsup": "*",
|
|
28
|
+
"typescript": "*",
|
|
29
|
+
"vitest": "*"
|
|
30
|
+
}
|
|
31
|
+
}
|