@rsalianto/git-heatmap-vanilla 0.1.1 → 0.1.2

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 CHANGED
@@ -5,13 +5,12 @@ declare class GitHeatmapElement extends HTMLElement {
5
5
  private _data;
6
6
  private _fetchData;
7
7
  private _shadow;
8
- private _tooltip;
9
8
  set data(value: HeatmapData);
10
9
  get data(): HeatmapData | null;
11
10
  set fetchData(fn: () => Promise<HeatmapData>);
12
11
  constructor();
13
12
  connectedCallback(): void;
14
- attributeChangedCallback(name: string, _old: string | null, _val: string | null): void;
13
+ attributeChangedCallback(name: string): void;
15
14
  private get _cellSize();
16
15
  private get _cellGap();
17
16
  private get _cellRadius();
@@ -21,11 +20,13 @@ declare class GitHeatmapElement extends HTMLElement {
21
20
  private get _showMonths();
22
21
  private get _showDays();
23
22
  private get _label();
23
+ private get _offsetX();
24
+ private get _offsetY();
24
25
  private _load;
25
26
  private _renderSkeleton;
26
27
  private _renderError;
27
28
  private _render;
28
- private _attachListeners;
29
+ private _tipText;
29
30
  }
30
31
 
31
32
  export { GitHeatmapElement };
package/dist/index.d.ts CHANGED
@@ -5,13 +5,12 @@ declare class GitHeatmapElement extends HTMLElement {
5
5
  private _data;
6
6
  private _fetchData;
7
7
  private _shadow;
8
- private _tooltip;
9
8
  set data(value: HeatmapData);
10
9
  get data(): HeatmapData | null;
11
10
  set fetchData(fn: () => Promise<HeatmapData>);
12
11
  constructor();
13
12
  connectedCallback(): void;
14
- attributeChangedCallback(name: string, _old: string | null, _val: string | null): void;
13
+ attributeChangedCallback(name: string): void;
15
14
  private get _cellSize();
16
15
  private get _cellGap();
17
16
  private get _cellRadius();
@@ -21,11 +20,13 @@ declare class GitHeatmapElement extends HTMLElement {
21
20
  private get _showMonths();
22
21
  private get _showDays();
23
22
  private get _label();
23
+ private get _offsetX();
24
+ private get _offsetY();
24
25
  private _load;
25
26
  private _renderSkeleton;
26
27
  private _renderError;
27
28
  private _render;
28
- private _attachListeners;
29
+ private _tipText;
29
30
  }
30
31
 
31
32
  export { GitHeatmapElement };
@@ -66,6 +66,8 @@ var GitHeatmapVanilla = (() => {
66
66
  var STEP = CELL + GAP;
67
67
 
68
68
  // src/git-heatmap-element.ts
69
+ var DAY_LABEL_W = 32;
70
+ var MONTH_LABEL_H = 16;
69
71
  var BASE_STYLES = `
70
72
  :host {
71
73
  --ghm-color-l0: rgba(255,255,255,0.08);
@@ -90,16 +92,9 @@ var GitHeatmapVanilla = (() => {
90
92
  }
91
93
  * { box-sizing: border-box; }
92
94
  .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; }
95
+ svg { display: block; overflow: visible; }
96
+ rect { transition: opacity 0.15s; cursor: pointer; }
97
+ rect:hover { opacity: 0.7; }
103
98
  .ghm-tooltip {
104
99
  pointer-events: none;
105
100
  position: absolute;
@@ -113,14 +108,17 @@ var GitHeatmapVanilla = (() => {
113
108
  padding: 3px 8px;
114
109
  white-space: nowrap;
115
110
  }
116
- .ghm-skeleton { opacity: 0.4; }
117
111
  `;
112
+ function svgEl(tag, attrs = {}) {
113
+ const el = document.createElementNS("http://www.w3.org/2000/svg", tag);
114
+ for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, String(v));
115
+ return el;
116
+ }
118
117
  var GitHeatmapElement = class extends HTMLElement {
119
118
  constructor() {
120
119
  super();
121
120
  this._data = null;
122
121
  this._fetchData = null;
123
- this._tooltip = null;
124
122
  this._shadow = this.attachShadow({ mode: "open" });
125
123
  }
126
124
  static get observedAttributes() {
@@ -139,15 +137,11 @@ var GitHeatmapVanilla = (() => {
139
137
  }
140
138
  connectedCallback() {
141
139
  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
- }
140
+ if (apiUrl && !this._data) this._load();
141
+ else if (this._data) this._render();
142
+ else this._renderSkeleton();
149
143
  }
150
- attributeChangedCallback(name, _old, _val) {
144
+ attributeChangedCallback(name) {
151
145
  if (name === "api-url") {
152
146
  this._load();
153
147
  return;
@@ -181,6 +175,12 @@ var GitHeatmapVanilla = (() => {
181
175
  get _label() {
182
176
  return this.getAttribute("label") ?? "Contribution heatmap";
183
177
  }
178
+ get _offsetX() {
179
+ return this._showDays ? DAY_LABEL_W : 0;
180
+ }
181
+ get _offsetY() {
182
+ return this._showMonths ? MONTH_LABEL_H + this._cellGap : 0;
183
+ }
184
184
  async _load() {
185
185
  const apiUrl = this.getAttribute("api-url");
186
186
  const resolver = this._fetchData ?? (apiUrl ? () => fetch(apiUrl).then((r) => r.json()) : null);
@@ -194,133 +194,140 @@ var GitHeatmapVanilla = (() => {
194
194
  }
195
195
  }
196
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>`;
197
+ const { _cellSize: cs, _cellGap: cg, _cellRadius: cr, _step: step, _offsetX: ox, _offsetY: oy } = this;
198
+ const w = 53 * step + ox;
199
+ const h = 7 * step - cg + oy;
200
+ const svg = svgEl("svg", { width: w, height: h });
201
+ for (let wi = 0; wi < 53; wi++) {
202
+ for (let dow = 0; dow < 7; dow++) {
203
+ svg.appendChild(svgEl("rect", { x: ox + wi * step, y: oy + dow * step, width: cs, height: cs, rx: cr, fill: "var(--ghm-color-l0)" }));
203
204
  }
204
- cols += `<div style="display:flex;flex-direction:column;gap:${gap}px;">${cells}</div>`;
205
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>`;
206
+ this._shadow.innerHTML = `<style>${BASE_STYLES}</style>`;
207
+ const wrap = document.createElement("div");
208
+ wrap.style.opacity = "0.4";
209
+ wrap.appendChild(svg);
210
+ this._shadow.appendChild(wrap);
210
211
  }
211
212
  _renderError() {
212
- this._shadow.innerHTML = `<style>${BASE_STYLES}</style>
213
- <p style="opacity:0.6;padding:1rem 0;">Could not load contribution data.</p>`;
213
+ this._shadow.innerHTML = `<style>${BASE_STYLES}</style><p style="opacity:0.6;padding:1rem 0;">Could not load contribution data.</p>`;
214
214
  }
215
215
  _render() {
216
216
  if (!this._data) return;
217
217
  const d = this._data;
218
- const cell = this._cellSize, gap = this._cellGap, r = this._cellRadius, step = this._step;
218
+ const { _cellSize: cs, _cellGap: cg, _cellRadius: cr, _step: step, _offsetX: ox, _offsetY: oy } = this;
219
219
  const levels = DEFAULT_LEVELS;
220
- const monthLabels = this._showMonths ? buildMonthLabels(d.weeks) : [];
221
- let monthRow = "";
220
+ const svgW = d.weeks.length * step + ox;
221
+ const svgH = 7 * step - cg + oy;
222
+ this._shadow.innerHTML = `<style>${BASE_STYLES}</style>`;
223
+ const wrapper = document.createElement("div");
224
+ wrapper.setAttribute("aria-label", this._label);
225
+ wrapper.style.position = "relative";
226
+ if (this._showTotal) {
227
+ const p = document.createElement("p");
228
+ p.style.cssText = "margin-bottom:12px; color:var(--ghm-text);";
229
+ p.textContent = `${d.totalContributions.toLocaleString()} contributions in the last year`;
230
+ wrapper.appendChild(p);
231
+ }
232
+ const scroll = document.createElement("div");
233
+ scroll.className = "ghm-scroll";
234
+ const svg = svgEl("svg", { width: svgW, height: svgH, role: "img", "aria-label": this._label });
222
235
  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>`;
236
+ const labels = buildMonthLabels(d.weeks);
237
+ for (const { label: ml, col } of labels) {
238
+ const t = svgEl("text", { x: ox + col * step, y: MONTH_LABEL_H - 4, fill: "var(--ghm-text)", "font-size": 10 });
239
+ t.textContent = ml;
240
+ svg.appendChild(t);
241
+ }
228
242
  }
229
- let dayLabelCol = "";
230
243
  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>`;
244
+ for (let dow = 0; dow < 7; dow++) {
245
+ const lbl = DAY_LABELS[dow];
246
+ if (!lbl) continue;
247
+ const t = svgEl("text", { x: DAY_LABEL_W - 6, y: oy + dow * step + cs, fill: "var(--ghm-text)", "font-size": 10, "text-anchor": "end" });
248
+ t.textContent = lbl;
249
+ svg.appendChild(t);
250
+ }
251
+ }
252
+ const mobileTip = document.createElement("div");
253
+ mobileTip.style.cssText = "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;";
254
+ let activeTapDate = "";
255
+ const tt = document.createElement("div");
256
+ tt.className = "ghm-tooltip";
257
+ tt.style.display = "none";
258
+ for (let wi = 0; wi < d.weeks.length; wi++) {
259
+ for (let dow = 0; dow < 7; dow++) {
260
+ const day = d.weeks[wi].days[dow];
261
+ if (!day?.date) continue;
262
+ const rect = svgEl("rect", {
263
+ x: ox + wi * step,
264
+ y: oy + dow * step,
265
+ width: cs,
266
+ height: cs,
267
+ rx: cr,
268
+ fill: `var(--ghm-color-l${day.level})`
269
+ });
270
+ rect.setAttribute("aria-label", this._tipText(day));
271
+ rect.setAttribute("role", "button");
272
+ rect.addEventListener("mouseenter", (e) => {
273
+ const r = rect.getBoundingClientRect();
274
+ const wr = wrapper.getBoundingClientRect();
275
+ tt.textContent = this._tipText(day);
276
+ tt.style.left = `${r.left - wr.left + cs / 2}px`;
277
+ tt.style.top = `${r.top - wr.top - 6}px`;
278
+ tt.style.display = "block";
279
+ });
280
+ rect.addEventListener("mouseleave", () => {
281
+ tt.style.display = "none";
282
+ });
283
+ rect.addEventListener("click", () => {
284
+ if (activeTapDate === day.date) {
285
+ activeTapDate = "";
286
+ mobileTip.style.display = "none";
287
+ } else {
288
+ activeTapDate = day.date;
289
+ mobileTip.textContent = this._tipText(day);
290
+ mobileTip.style.display = "block";
291
+ }
292
+ this.dispatchEvent(new CustomEvent("day-click", { detail: { date: day.date, count: day.count }, bubbles: true, composed: true }));
293
+ });
294
+ svg.appendChild(rect);
295
+ }
236
296
  }
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 = "";
297
+ scroll.appendChild(svg);
298
+ wrapper.appendChild(scroll);
299
+ requestAnimationFrame(() => {
300
+ scroll.scrollLeft = scroll.scrollWidth;
301
+ });
302
+ wrapper.appendChild(mobileTip);
256
303
  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>`;
304
+ const legend = document.createElement("div");
305
+ legend.style.cssText = "display:flex; align-items:center; justify-content:flex-end; margin-top:8px;";
306
+ const inner = document.createElement("div");
307
+ inner.style.cssText = "display:flex; align-items:center; gap:6px; font-size:0.9em;";
308
+ const less = document.createElement("span");
309
+ less.textContent = "Less";
310
+ const more = document.createElement("span");
311
+ more.textContent = "More";
312
+ inner.appendChild(less);
313
+ for (let i = 0; i < levels.length; i++) {
314
+ const s = svgEl("svg", { width: cs, height: cs });
315
+ const r = svgEl("rect", { width: cs, height: cs, rx: cr, fill: `var(--ghm-color-l${i})` });
316
+ const title = svgEl("title");
317
+ title.textContent = levels[i].label;
318
+ r.appendChild(title);
319
+ s.appendChild(r);
320
+ inner.appendChild(s);
321
+ }
322
+ inner.appendChild(more);
323
+ legend.appendChild(inner);
324
+ wrapper.appendChild(legend);
265
325
  }
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;
326
+ wrapper.appendChild(tt);
327
+ this._shadow.appendChild(wrapper);
286
328
  }
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
- });
329
+ _tipText(day) {
330
+ return day.count === 0 ? `No contributions on ${day.date}` : `${day.count} contribution${day.count > 1 ? "s" : ""} on ${day.date}`;
324
331
  }
325
332
  };
326
333
  if (typeof customElements !== "undefined" && !customElements.get("git-heatmap")) {
@@ -1 +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":[]}
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, DAY_LABELS,\n DEFAULT_LEVELS,\n} from \"@rsalianto/git-heatmap-core\";\nimport type { HeatmapData, HeatmapDay, LevelConfig } from \"@rsalianto/git-heatmap-core\";\n\nconst DAY_LABEL_W = 32;\nconst MONTH_LABEL_H = 16;\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 svg { display: block; overflow: visible; }\n rect { transition: opacity 0.15s; cursor: pointer; }\n rect: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`;\n\nfunction svgEl(tag: string, attrs: Record<string, string | number> = {}): SVGElement {\n const el = document.createElementNS(\"http://www.w3.org/2000/svg\", tag);\n for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, String(v));\n return el;\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\n set data(value: HeatmapData) { this._data = value; this._render(); }\n get data(): HeatmapData | null { return this._data; }\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) this._load();\n else if (this._data) this._render();\n else this._renderSkeleton();\n }\n\n attributeChangedCallback(name: string) {\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 get _offsetX() { return this._showDays ? DAY_LABEL_W : 0; }\n private get _offsetY() { return this._showMonths ? MONTH_LABEL_H + this._cellGap : 0; }\n\n private async _load() {\n const apiUrl = this.getAttribute(\"api-url\");\n const resolver = this._fetchData ?? (apiUrl ? () => fetch(apiUrl).then(r => r.json()) : null);\n if (!resolver) return;\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 { _cellSize: cs, _cellGap: cg, _cellRadius: cr, _step: step, _offsetX: ox, _offsetY: oy } = this;\n const w = 53 * step + ox;\n const h = 7 * step - cg + oy;\n const svg = svgEl(\"svg\", { width: w, height: h });\n for (let wi = 0; wi < 53; wi++) {\n for (let dow = 0; dow < 7; dow++) {\n svg.appendChild(svgEl(\"rect\", { x: ox + wi * step, y: oy + dow * step, width: cs, height: cs, rx: cr, fill: \"var(--ghm-color-l0)\" }));\n }\n }\n this._shadow.innerHTML = `<style>${BASE_STYLES}</style>`;\n const wrap = document.createElement(\"div\");\n wrap.style.opacity = \"0.4\";\n wrap.appendChild(svg);\n this._shadow.appendChild(wrap);\n }\n\n private _renderError() {\n this._shadow.innerHTML = `<style>${BASE_STYLES}</style><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 { _cellSize: cs, _cellGap: cg, _cellRadius: cr, _step: step, _offsetX: ox, _offsetY: oy } = this;\n const levels: LevelConfig[] = DEFAULT_LEVELS;\n\n const svgW = d.weeks.length * step + ox;\n const svgH = 7 * step - cg + oy;\n\n this._shadow.innerHTML = `<style>${BASE_STYLES}</style>`;\n const wrapper = document.createElement(\"div\");\n wrapper.setAttribute(\"aria-label\", this._label);\n wrapper.style.position = \"relative\";\n\n // Total\n if (this._showTotal) {\n const p = document.createElement(\"p\");\n p.style.cssText = \"margin-bottom:12px; color:var(--ghm-text);\";\n p.textContent = `${d.totalContributions.toLocaleString()} contributions in the last year`;\n wrapper.appendChild(p);\n }\n\n // Scroll + SVG\n const scroll = document.createElement(\"div\");\n scroll.className = \"ghm-scroll\";\n const svg = svgEl(\"svg\", { width: svgW, height: svgH, role: \"img\", \"aria-label\": this._label });\n\n // Month labels\n if (this._showMonths) {\n const labels = buildMonthLabels(d.weeks);\n for (const { label: ml, col } of labels) {\n const t = svgEl(\"text\", { x: ox + col * step, y: MONTH_LABEL_H - 4, fill: \"var(--ghm-text)\", \"font-size\": 10 });\n t.textContent = ml;\n svg.appendChild(t);\n }\n }\n\n // Day labels\n if (this._showDays) {\n for (let dow = 0; dow < 7; dow++) {\n const lbl = DAY_LABELS[dow];\n if (!lbl) continue;\n const t = svgEl(\"text\", { x: DAY_LABEL_W - 6, y: oy + dow * step + cs, fill: \"var(--ghm-text)\", \"font-size\": 10, \"text-anchor\": \"end\" });\n t.textContent = lbl;\n svg.appendChild(t);\n }\n }\n\n // Grid cells\n const mobileTip = document.createElement(\"div\");\n mobileTip.style.cssText = \"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;\";\n let activeTapDate = \"\";\n\n const tt = document.createElement(\"div\");\n tt.className = \"ghm-tooltip\";\n tt.style.display = \"none\";\n\n for (let wi = 0; wi < d.weeks.length; wi++) {\n for (let dow = 0; dow < 7; dow++) {\n const day = d.weeks[wi].days[dow] as HeatmapDay | undefined;\n if (!day?.date) continue;\n const rect = svgEl(\"rect\", {\n x: ox + wi * step, y: oy + dow * step,\n width: cs, height: cs, rx: cr,\n fill: `var(--ghm-color-l${day.level})`,\n }) as SVGRectElement;\n rect.setAttribute(\"aria-label\", this._tipText(day));\n rect.setAttribute(\"role\", \"button\");\n\n rect.addEventListener(\"mouseenter\", (e) => {\n const r = rect.getBoundingClientRect();\n const wr = wrapper.getBoundingClientRect();\n tt.textContent = this._tipText(day);\n tt.style.left = `${r.left - wr.left + cs / 2}px`;\n tt.style.top = `${r.top - wr.top - 6}px`;\n tt.style.display = \"block\";\n });\n rect.addEventListener(\"mouseleave\", () => { tt.style.display = \"none\"; });\n rect.addEventListener(\"click\", () => {\n if (activeTapDate === day.date) {\n activeTapDate = \"\";\n mobileTip.style.display = \"none\";\n } else {\n activeTapDate = day.date;\n mobileTip.textContent = this._tipText(day);\n mobileTip.style.display = \"block\";\n }\n this.dispatchEvent(new CustomEvent(\"day-click\", { detail: { date: day.date, count: day.count }, bubbles: true, composed: true }));\n });\n\n svg.appendChild(rect);\n }\n }\n\n scroll.appendChild(svg);\n wrapper.appendChild(scroll);\n\n // Scroll to end\n requestAnimationFrame(() => { scroll.scrollLeft = scroll.scrollWidth; });\n\n wrapper.appendChild(mobileTip);\n\n // Legend\n if (this._showLegend) {\n const legend = document.createElement(\"div\");\n legend.style.cssText = \"display:flex; align-items:center; justify-content:flex-end; margin-top:8px;\";\n const inner = document.createElement(\"div\");\n inner.style.cssText = \"display:flex; align-items:center; gap:6px; font-size:0.9em;\";\n const less = document.createElement(\"span\"); less.textContent = \"Less\";\n const more = document.createElement(\"span\"); more.textContent = \"More\";\n inner.appendChild(less);\n for (let i = 0; i < levels.length; i++) {\n const s = svgEl(\"svg\", { width: cs, height: cs }) as SVGSVGElement & Element;\n const r = svgEl(\"rect\", { width: cs, height: cs, rx: cr, fill: `var(--ghm-color-l${i})` });\n const title = svgEl(\"title\"); title.textContent = levels[i].label;\n r.appendChild(title);\n s.appendChild(r);\n inner.appendChild(s as unknown as Node);\n }\n inner.appendChild(more);\n legend.appendChild(inner);\n wrapper.appendChild(legend);\n }\n\n wrapper.appendChild(tt);\n this._shadow.appendChild(wrapper);\n }\n\n private _tipText(day: HeatmapDay): string {\n return day.count === 0\n ? `No contributions on ${day.date}`\n : `${day.count} contribution${day.count > 1 ? \"s\" : \"\"} on ${day.date}`;\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":[]}