{"version":3,"file":"ngwr-calendar-heatmap.mjs","sources":["../../../projects/lib/calendar-heatmap/calendar-heatmap.ts","../../../projects/lib/calendar-heatmap/calendar-heatmap.html","../../../projects/lib/calendar-heatmap/ngwr-calendar-heatmap.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';\nimport { Component, ViewEncapsulation, computed, input } from '@angular/core';\n\nimport type { WrHeatmapDatum } from './interfaces';\n\ninterface Cell {\n  readonly iso: string;\n  readonly value: number;\n  readonly intensity: number;\n  readonly week: number;\n  readonly day: number;\n}\n\nfunction toIso(d: Date): string {\n  const y = d.getFullYear();\n  const m = String(d.getMonth() + 1).padStart(2, '0');\n  const day = String(d.getDate()).padStart(2, '0');\n  return `${y}-${m}-${day}`;\n}\n\nfunction parseInput(value: string | Date): Date {\n  return value instanceof Date ? new Date(value) : new Date(`${value}T00:00:00`);\n}\n\nconst WEEKDAY_LABELS = ['', 'Mon', '', 'Wed', '', 'Fri', ''];\nconst MONTH_LABELS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];\n\n/**\n * GitHub-style year-grid heatmap. One column per ISO week, one row per\n * weekday — cells coloured by `value` relative to the data set's max.\n *\n * @example\n * ```html\n * <wr-calendar-heatmap [data]=\"contributions\" />\n * <wr-calendar-heatmap [data]=\"data\" [endDate]=\"today\" [weeks]=\"26\" color=\"#10b981\" />\n * ```\n *\n * @see https://ngwr.dev/components/calendar-heatmap\n */\n@Component({\n  selector: 'wr-calendar-heatmap',\n  templateUrl: './calendar-heatmap.html',\n  encapsulation: ViewEncapsulation.None,\n  host: { class: 'wr-calendar-heatmap' },\n})\nexport class WrCalendarHeatmap {\n  readonly data = input<readonly WrHeatmapDatum[]>([]);\n\n  /** Last day to render. @default today */\n  readonly endDate = input<string | Date | null>(null);\n\n  /** Number of weeks (columns) to render. @default 53 */\n  readonly weeks = input(53, { transform: (v: unknown): number => Math.max(4, coerceNumberProperty(v, 53)) });\n\n  /** Cell side in CSS pixels. @default 11 */\n  readonly cellSize = input(11, { transform: (v: unknown): number => Math.max(4, coerceNumberProperty(v, 11)) });\n\n  /** Pixel gap between cells. @default 2 */\n  readonly cellGap = input(2, { transform: (v: unknown): number => Math.max(0, coerceNumberProperty(v, 2)) });\n\n  /** Cell fill colour at full intensity. @default primary */\n  readonly color = input<string>('var(--wr-color-primary)');\n\n  /** Background colour for zero-value days. @default light tint */\n  readonly emptyColor = input<string>('rgba(var(--wr-color-light-rgb), 0.5)');\n\n  /** Show the weekday + month labels around the grid. @default true */\n  readonly showLabels = input(true, { transform: coerceBooleanProperty });\n\n  protected readonly weekdayLabels = WEEKDAY_LABELS;\n\n  protected readonly cells = computed<readonly Cell[]>(() => {\n    const end = this.endDate() ? parseInput(this.endDate()!) : new Date();\n    end.setHours(0, 0, 0, 0);\n    const weeks = this.weeks();\n    const totalDays = weeks * 7;\n\n    // Build value lookup.\n    const map = new Map<string, number>();\n    for (const d of this.data()) {\n      const iso = toIso(parseInput(d.date));\n      map.set(iso, (map.get(iso) ?? 0) + d.value);\n    }\n    const max = [...map.values()].reduce((a, b) => Math.max(a, b), 0);\n\n    // Walk backwards from end day; arrange into columns by week, rows by weekday.\n    const out: Cell[] = [];\n    const start = new Date(end);\n    start.setDate(end.getDate() - totalDays + 1);\n    // Align start so column 0 begins on a Sunday (matches GitHub).\n    start.setDate(start.getDate() - start.getDay());\n\n    const cursor = new Date(start);\n    let column = 0;\n    while (cursor <= end) {\n      for (let row = 0; row < 7; row++) {\n        const iso = toIso(cursor);\n        const value = map.get(iso) ?? 0;\n        const intensity = max > 0 ? Math.min(1, value / max) : 0;\n        out.push({ iso, value, intensity, week: column, day: row });\n        cursor.setDate(cursor.getDate() + 1);\n      }\n      column++;\n    }\n    return out;\n  });\n\n  protected readonly monthLabels = computed(() => {\n    // Pull the first cell of each visible month → render a label centered\n    // over its column.\n    const cells = this.cells();\n    const seen = new Set<string>();\n    return cells\n      .filter(cell => {\n        const date = new Date(`${cell.iso}T00:00:00`);\n        if (date.getDate() !== 1) return false;\n        const key = `${date.getFullYear()}-${date.getMonth()}`;\n        if (seen.has(key)) return false;\n        seen.add(key);\n        return true;\n      })\n      .map(cell => ({\n        label: MONTH_LABELS[new Date(`${cell.iso}T00:00:00`).getMonth()],\n        week: cell.week,\n      }));\n  });\n\n  protected readonly columnCount = computed(() => {\n    const cells = this.cells();\n    return cells.length === 0 ? 0 : cells[cells.length - 1].week + 1;\n  });\n\n  protected colorFor(intensity: number): string {\n    if (intensity === 0) return this.emptyColor();\n    return this.color();\n  }\n\n  protected opacityFor(intensity: number): number {\n    if (intensity === 0) return 1;\n    // 4 levels — match the GitHub aesthetic.\n    if (intensity < 0.25) return 0.25;\n    if (intensity < 0.5) return 0.5;\n    if (intensity < 0.75) return 0.75;\n    return 1;\n  }\n}\n\nexport type { WrHeatmapDatum } from './interfaces';\n","<div\n  class=\"wr-calendar-heatmap__grid\"\n  [style.--wr-heatmap-cell-size.px]=\"cellSize()\"\n  [style.--wr-heatmap-cell-gap.px]=\"cellGap()\"\n  [style.--wr-heatmap-columns]=\"columnCount()\"\n>\n  @if (showLabels()) {\n    <div class=\"wr-calendar-heatmap__weekdays\">\n      @for (label of weekdayLabels; track $index) {\n        <span class=\"wr-calendar-heatmap__weekday\">{{ label }}</span>\n      }\n    </div>\n  }\n\n  <div class=\"wr-calendar-heatmap__cells\">\n    @for (cell of cells(); track cell.iso) {\n      <span\n        class=\"wr-calendar-heatmap__cell\"\n        [style.background]=\"colorFor(cell.intensity)\"\n        [style.opacity]=\"opacityFor(cell.intensity)\"\n        [style.grid-column]=\"cell.week + 1\"\n        [style.grid-row]=\"cell.day + 1\"\n        [title]=\"`${cell.iso}: ${cell.value}`\"\n      ></span>\n    }\n  </div>\n\n  @if (showLabels()) {\n    <div class=\"wr-calendar-heatmap__months\">\n      @for (m of monthLabels(); track $index) {\n        <span class=\"wr-calendar-heatmap__month\" [style.grid-column]=\"m.week + 1\">{{ m.label }}</span>\n      }\n    </div>\n  }\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}