@0001-labs/design-system-1 0.3.0-alpha.21 → 0.3.0-alpha.22

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.
@@ -173,8 +173,31 @@ export type CalendarEvent = {
173
173
  time?: string;
174
174
  /** A colour token or value for the event's fill (`var(--ds-zenith-blue)`). */
175
175
  color?: string;
176
+ /** How much it counts for in a calendar's heat (commits, purchases, hours); 1 when left out. */
177
+ count?: number;
176
178
  };
177
179
 
180
+ /** A day's heat: 1 (a little) to 4 (the most). */
181
+ export type HeatLevel = 1 | 2 | 3 | 4;
182
+
183
+ /**
184
+ * How strongly each day is lit, as a contribution graph lights it: the
185
+ * amounts of its events added up, in four steps of the busiest day (or of
186
+ * `max`, for a scale that stays put from month to month). A day with nothing
187
+ * has no level.
188
+ */
189
+ export function heatLevels(byDay: ReadonlyMap<Day, readonly CalendarEvent[]>, max?: number): Map<Day, HeatLevel> {
190
+ const amounts = new Map<Day, number>();
191
+ for (const [day, events] of byDay) {
192
+ const amount = events.reduce((sum, event) => sum + (typeof event.count === "number" && Number.isFinite(event.count) ? Math.max(0, event.count) : 1), 0);
193
+ if (amount > 0) amounts.set(day, amount);
194
+ }
195
+ const top = typeof max === "number" && Number.isFinite(max) && max > 0 ? max : Math.max(0, ...amounts.values());
196
+ const levels = new Map<Day, HeatLevel>();
197
+ for (const [day, amount] of amounts) levels.set(day, Math.min(4, Math.max(1, Math.ceil((amount / top) * 4))) as HeatLevel);
198
+ return levels;
199
+ }
200
+
178
201
  /** The events that touch each day between `from` and `to`, in order: longest first, then by time. */
179
202
  export function eventsByDay(events: readonly CalendarEvent[], from: Day, to: Day): Map<Day, CalendarEvent[]> {
180
203
  const days = new Map<Day, CalendarEvent[]>();
@@ -1,4 +1,4 @@
1
- /* version 0.3.0-alpha.21 */
1
+ /* version 0.3.0-alpha.22 */
2
2
 
3
3
  /* Noto Sans for Japanese and Chinese, for a product that shows them. Opt in:
4
4
  link it beside one.css or fonts.css, or ask Sync 1 for "cjk": true. Without
@@ -1,4 +1,4 @@
1
- /* version 0.3.0-alpha.21 */
1
+ /* version 0.3.0-alpha.22 */
2
2
 
3
3
  /* Design system 1's faces and the typeface tokens that name them, on their
4
4
  own: a page that does not load one.css links this sheet (one.css imports
@@ -1,4 +1,4 @@
1
- /* version 0.3.0-alpha.21 */
1
+ /* version 0.3.0-alpha.22 */
2
2
 
3
3
  @import "./grid.css";
4
4
  @import "./component-tokens.css";
@@ -1,4 +1,4 @@
1
- /* DS1 — version 0.3.0-alpha.21 */
1
+ /* DS1 — version 0.3.0-alpha.22 */
2
2
  /* A classless stylesheet for native HTML. No JavaScript is required. */
3
3
 
4
4
  /* It draws on Design system 1's measures: the 20px square, 14px type on
@@ -21,6 +21,7 @@ import {
21
21
  dateNumber,
22
22
  dayOf,
23
23
  eventsByDay,
24
+ heatLevels,
24
25
  isDay,
25
26
  isoWeek,
26
27
  monthTitle,
@@ -30,6 +31,7 @@ import {
30
31
  weekdayNames,
31
32
  type CalendarEvent,
32
33
  type Day,
34
+ type HeatLevel,
33
35
  } from "../0-face/calendar";
34
36
  import { CalendarBase, readMonth, readRange, type CalendarChangeDetail } from "./calendar-base";
35
37
  import "../2-core/ds-icon";
@@ -105,6 +107,12 @@ const openPickers = new Set<Calendar>();
105
107
  * @prop {boolean} fixed - Always draw six weeks, so the month keeps its height.
106
108
  * @prop {boolean} outsideDays - Draw the days of the months before and after
107
109
  * in the title grey (`outside-days`); pressing one moves there.
110
+ * @prop {boolean} heat - Light each day by how much happens on it, as a
111
+ * contribution graph does: the events' `count`s added up (one each by
112
+ * default), in four steps of the busiest day shown.
113
+ * @prop {number} heatMax - The amount that lights a day fully (`heat-max`),
114
+ * for a scale that stays put from month to month; the busiest day shown
115
+ * by default.
108
116
  * @prop {boolean} noHeader - Leave out the title and arrows (`no-header`).
109
117
  * @prop {boolean} noToday - Leave out the Today button (`no-today`).
110
118
  * @prop {string} min - The first day a person may choose.
@@ -153,6 +161,8 @@ export class Calendar extends CalendarBase {
153
161
  outsideDays: { type: Boolean, attribute: "outside-days", reflect: true },
154
162
  noHeader: { type: Boolean, attribute: "no-header", reflect: true },
155
163
  noToday: { type: Boolean, attribute: "no-today", reflect: true },
164
+ heat: { type: Boolean, reflect: true },
165
+ heatMax: { type: Number, attribute: "heat-max" },
156
166
  picker: { type: Boolean, reflect: true },
157
167
  name: { type: String, reflect: true },
158
168
  placeholder: { type: String },
@@ -175,6 +185,8 @@ export class Calendar extends CalendarBase {
175
185
  declare outsideDays: boolean;
176
186
  declare noHeader: boolean;
177
187
  declare noToday: boolean;
188
+ declare heat: boolean;
189
+ declare heatMax: number | undefined;
178
190
  declare picker: boolean;
179
191
  declare name: string | undefined;
180
192
  declare placeholder: string | undefined;
@@ -189,6 +201,7 @@ export class Calendar extends CalendarBase {
189
201
  protected readonly weeksRun = "rows" as const;
190
202
 
191
203
  private byDay = new Map<Day, CalendarEvent[]>();
204
+ private heatByDay = new Map<Day, HeatLevel>();
192
205
  private readonly internals = typeof this.attachInternals === "function" ? this.attachInternals() : null;
193
206
  /** The value the field started with, for a form's reset. */
194
207
  private initial = "";
@@ -204,6 +217,7 @@ export class Calendar extends CalendarBase {
204
217
  this.outsideDays = false;
205
218
  this.noHeader = false;
206
219
  this.noToday = false;
220
+ this.heat = false;
207
221
  this.picker = false;
208
222
  this.time = false;
209
223
  this.required = false;
@@ -400,7 +414,8 @@ export class Calendar extends CalendarBase {
400
414
  const unfolded = this.unfolded === day;
401
415
  const shown = unfolded || events.length <= lines ? events : events.slice(0, Math.max(0, lines - 1));
402
416
  const hidden = events.length - shown.length;
403
- const parts = ["day", isToday ? "today" : "", selected ? "selected" : ""].filter(Boolean).join(" ");
417
+ const heat = this.heatByDay.get(day);
418
+ const parts = ["day", isToday ? "today" : "", selected ? "selected" : "", heat ? "heat" : ""].filter(Boolean).join(" ");
404
419
  return html`<div
405
420
  role="gridcell"
406
421
  class="day"
@@ -418,6 +433,7 @@ export class Calendar extends CalendarBase {
418
433
  ?data-last=${last}
419
434
  ?data-outside=${outside}
420
435
  ?data-busy=${events.length > 0}
436
+ data-heat=${heat ?? nothing}
421
437
  ?data-open=${unfolded}
422
438
  >
423
439
  <span class="date-line" aria-hidden="true">
@@ -444,6 +460,8 @@ export class Calendar extends CalendarBase {
444
460
  const first = this.firstDay;
445
461
  const weeks = monthWeeks(month, first, this.fixed);
446
462
  this.byDay = eventsByDay(this.events ?? [], weeks[0][0], weeks.at(-1)![6]);
463
+ // Heat is measured over the days of the month shown, so the neighbours' days never set its scale.
464
+ this.heatByDay = this.heat ? heatLevels(new Map([...this.byDay].filter(([day]) => sameMonth(day, month))), this.heatMax) : new Map();
447
465
  // A compact month names its days in a letter (one square) or two (wider); a full month in short.
448
466
  const short = this.compact
449
467
  ? this.squaresPerDay < 2
@@ -25,6 +25,17 @@
25
25
  --ds-calendar-band: color-mix(in srgb, var(--ds-calendar-selected) 16%, var(--ds-paper));
26
26
  --ds-calendar-mark: calc(var(--ds-unit) * 0.9);
27
27
  --ds-calendar-pad: calc(4px * var(--ds-sf, 1));
28
+ /* Heat, in four steps of the accent: its pastel, between, the colour, and
29
+ past it (deepened with the ink on light paper, the full colour on dark).
30
+ The words are the ink on every step but the two darkest on dark paper. */
31
+ --ds-calendar-heat-1: var(--one-apple-green-pastel, color-mix(in srgb, var(--ds-apple-green) 40%, var(--ds-paper)));
32
+ --ds-calendar-heat-3: var(--one-apple-green, var(--ds-apple-green));
33
+ --ds-calendar-heat-2: color-mix(in srgb, var(--ds-calendar-heat-1), var(--ds-calendar-heat-3));
34
+ --ds-calendar-heat-4: light-dark(color-mix(in srgb, var(--ds-apple-green) 60%, var(--ds-slate)), var(--ds-apple-green));
35
+ --ds-calendar-heat-ink-1: light-dark(var(--ds-slate), var(--ds-white));
36
+ --ds-calendar-heat-ink-2: light-dark(var(--ds-slate), var(--ds-white));
37
+ --ds-calendar-heat-ink-3: var(--ds-slate);
38
+ --ds-calendar-heat-ink-4: var(--ds-slate);
28
39
  box-sizing: border-box;
29
40
  display: inline-block;
30
41
  vertical-align: top;
@@ -175,6 +186,20 @@
175
186
 
176
187
  .day[aria-disabled="true"] .date { color: var(--ds-title-color); opacity: 0.5; }
177
188
 
189
+ /* Heat: the date's mark filled by its step, a picture's corners, inside the day's square; today and the selection draw over it. */
190
+ .day[data-heat]:not([data-today], [data-selected]) .date-line { align-items: center; padding-inline-end: calc(var(--ds-unit) * 0.05); }
191
+ .day[data-heat]:not([data-today], [data-selected]) .date {
192
+ min-width: var(--ds-calendar-mark);
193
+ height: var(--ds-calendar-mark);
194
+ border-radius: var(--ds-radius);
195
+ letter-spacing: 0;
196
+ }
197
+ .day[data-heat="1"]:not([data-today], [data-selected]) .date { background: var(--ds-calendar-heat-1); color: var(--ds-calendar-heat-ink-1); }
198
+ .day[data-heat="2"]:not([data-today], [data-selected]) .date { background: var(--ds-calendar-heat-2); color: var(--ds-calendar-heat-ink-2); }
199
+ .day[data-heat="3"]:not([data-today], [data-selected]) .date { background: var(--ds-calendar-heat-3); color: var(--ds-calendar-heat-ink-3); }
200
+ .day[data-heat="4"]:not([data-today], [data-selected]) .date { background: var(--ds-calendar-heat-4); color: var(--ds-calendar-heat-ink-4); }
201
+ :host([compact]) .day[data-heat]:not([data-today], [data-selected]) .date-line { padding: 0; }
202
+
178
203
  /* Events: one square each, stacked edge to edge on the day's lines, drawn one pixel short. */
179
204
  .events {
180
205
  display: flex;
@@ -69,7 +69,18 @@ export type CalendarEvent = {
69
69
  time?: string;
70
70
  /** A colour token or value for the event's fill (`var(--ds-zenith-blue)`). */
71
71
  color?: string;
72
+ /** How much it counts for in a calendar's heat (commits, purchases, hours); 1 when left out. */
73
+ count?: number;
72
74
  };
75
+ /** A day's heat: 1 (a little) to 4 (the most). */
76
+ export type HeatLevel = 1 | 2 | 3 | 4;
77
+ /**
78
+ * How strongly each day is lit, as a contribution graph lights it: the
79
+ * amounts of its events added up, in four steps of the busiest day (or of
80
+ * `max`, for a scale that stays put from month to month). A day with nothing
81
+ * has no level.
82
+ */
83
+ export declare function heatLevels(byDay: ReadonlyMap<Day, readonly CalendarEvent[]>, max?: number): Map<Day, HeatLevel>;
73
84
  /** The events that touch each day between `from` and `to`, in order: longest first, then by time. */
74
85
  export declare function eventsByDay(events: readonly CalendarEvent[], from: Day, to: Day): Map<Day, CalendarEvent[]>;
75
86
  //# sourceMappingURL=calendar.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"calendar.d.ts","sourceRoot":"","sources":["../../DS1/0-face/calendar.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"calendar.d.ts","sourceRoot":"","sources":["../../DS1/0-face/calendar.ts"],"names":[],"mappings":"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"}
@@ -129,6 +129,25 @@ export function isoWeek(day) {
129
129
  const firstThursday = addDays(january4, 3 - ((weekdayOf(january4) + 6) % 7));
130
130
  return 1 + Math.round((noon(thursday) - noon(firstThursday)) / (7 * 864e5));
131
131
  }
132
+ /**
133
+ * How strongly each day is lit, as a contribution graph lights it: the
134
+ * amounts of its events added up, in four steps of the busiest day (or of
135
+ * `max`, for a scale that stays put from month to month). A day with nothing
136
+ * has no level.
137
+ */
138
+ export function heatLevels(byDay, max) {
139
+ const amounts = new Map();
140
+ for (const [day, events] of byDay) {
141
+ const amount = events.reduce((sum, event) => sum + (typeof event.count === "number" && Number.isFinite(event.count) ? Math.max(0, event.count) : 1), 0);
142
+ if (amount > 0)
143
+ amounts.set(day, amount);
144
+ }
145
+ const top = typeof max === "number" && Number.isFinite(max) && max > 0 ? max : Math.max(0, ...amounts.values());
146
+ const levels = new Map();
147
+ for (const [day, amount] of amounts)
148
+ levels.set(day, Math.min(4, Math.max(1, Math.ceil((amount / top) * 4))));
149
+ return levels;
150
+ }
132
151
  /** The events that touch each day between `from` and `to`, in order: longest first, then by time. */
133
152
  export function eventsByDay(events, from, to) {
134
153
  const days = new Map();
@@ -44,6 +44,12 @@ export declare function parseTypedDay(text: string): Day | null;
44
44
  * @prop {boolean} fixed - Always draw six weeks, so the month keeps its height.
45
45
  * @prop {boolean} outsideDays - Draw the days of the months before and after
46
46
  * in the title grey (`outside-days`); pressing one moves there.
47
+ * @prop {boolean} heat - Light each day by how much happens on it, as a
48
+ * contribution graph does: the events' `count`s added up (one each by
49
+ * default), in four steps of the busiest day shown.
50
+ * @prop {number} heatMax - The amount that lights a day fully (`heat-max`),
51
+ * for a scale that stays put from month to month; the busiest day shown
52
+ * by default.
47
53
  * @prop {boolean} noHeader - Leave out the title and arrows (`no-header`).
48
54
  * @prop {boolean} noToday - Leave out the Today button (`no-today`).
49
55
  * @prop {string} min - The first day a person may choose.
@@ -120,6 +126,14 @@ export declare class Calendar extends CalendarBase {
120
126
  attribute: string;
121
127
  reflect: boolean;
122
128
  };
129
+ heat: {
130
+ type: BooleanConstructor;
131
+ reflect: boolean;
132
+ };
133
+ heatMax: {
134
+ type: NumberConstructor;
135
+ attribute: string;
136
+ };
123
137
  picker: {
124
138
  type: BooleanConstructor;
125
139
  reflect: boolean;
@@ -204,6 +218,8 @@ export declare class Calendar extends CalendarBase {
204
218
  outsideDays: boolean;
205
219
  noHeader: boolean;
206
220
  noToday: boolean;
221
+ heat: boolean;
222
+ heatMax: number | undefined;
207
223
  picker: boolean;
208
224
  name: string | undefined;
209
225
  placeholder: string | undefined;
@@ -216,6 +232,7 @@ export declare class Calendar extends CalendarBase {
216
232
  unfolded: Day | undefined;
217
233
  protected readonly weeksRun: "rows";
218
234
  private byDay;
235
+ private heatByDay;
219
236
  private readonly internals;
220
237
  /** The value the field started with, for a form's reset. */
221
238
  private initial;
@@ -1 +1 @@
1
- {"version":3,"file":"ds-calendar.d.ts","sourceRoot":"","sources":["../../DS1/3-unit/ds-calendar.ts"],"names":[],"mappings":"AAcA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAGpE,OAAO,EAAiB,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAClE,OAAO,EAYL,KAAK,aAAa,EAClB,KAAK,GAAG,EACT,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAmD,MAAM,iBAAiB,CAAC;AAChG,OAAO,mBAAmB,CAAC;AAK3B,cAAc,iBAAiB,CAAC;AAKhC,qFAAqF;AACrF,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,GAAG,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;CACd;AAID,qFAAqF;AACrF,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG;IAAE,KAAK,CAAC,EAAE,WAAW,CAAC;IAAC,GAAG,CAAC,EAAE,WAAW,CAAA;CAAE,CAW1G;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,EAAE,GAAG,EAAE,WAAW,GAAG,SAAS,EAAE,OAAO,EAAE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG,MAAM,CAI/H;AAED,8GAA8G;AAC9G,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI,CAKtD;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,qBAAa,QAAS,SAAQ,YAAY;IACxC,MAAM,CAAC,cAAc,UAAQ;IAE7B,MAAM,CAAC,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAoBf;IAEF,MAAM,CAAC,MAAM,4BAAgE;IAErE,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,WAAW,EAAE,OAAO,CAAC;IACrB,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,MAAM,EAAE,UAAU,GAAG,SAAS,CAAC;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,OAAO,CAAC;IACtB,gDAAgD;IACxC,QAAQ,EAAE,GAAG,GAAG,SAAS,CAAC;IAElC,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAG,MAAM,CAAU;IAE9C,OAAO,CAAC,KAAK,CAAmC;IAChD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA8E;IACxG,4DAA4D;IAC5D,OAAO,CAAC,OAAO,CAAM;IACrB,OAAO,CAAC,KAAK,CAAS;IACtB,mFAAmF;IACnF,OAAO,CAAC,UAAU,CAAS;;IAiB3B,oBAAoB;IAKpB,SAAS,CAAC,YAAY;IAItB,6DAA6D;IAC7D,iBAAiB;IAIjB,oBAAoB,CAAC,QAAQ,EAAE,OAAO;IAItC,oCAAoC;IACpC,IAAI,UAAU,IAAI,GAAG,CAEpB;IAED,OAAO,KAAK,aAAa,GAExB;IAED,OAAO,KAAK,UAAU,GAErB;IAED,SAAS,CAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,aAAa,EAAE;IAI7C,SAAS,CAAC,UAAU,CAAC,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC;IAgBlD,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC;IAa/C,uDAAuD;IACvD,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,UAAQ;IAQzC,oDAAoD;IACpD,aAAa;IAIb,+CAA+C;IAC/C,SAAS;IAIT,+CAA+C;IAC/C,SAAS;IAKT,6FAA6F;IAC7F,OAAO,KAAK,MAAM,GAIjB;IAED,OAAO,CAAC,SAAS,CAmBf;IAEF,OAAO,CAAC,OAAO,CAqBb;IAEF,8EAA8E;IAC9E,OAAO,CAAC,aAAa,CAKnB;IAEF,OAAO,CAAC,UAAU,CAEhB;IAEF,OAAO,CAAC,YAAY;IAyBpB,OAAO,CAAC,SAAS;IAkDjB,MAAM;IAkCN,OAAO,CAAC,WAAW;IAKnB,OAAO,KAAK,OAAO,GAElB;IAED,OAAO,KAAK,KAAK,GAEhB;IAED,yCAAyC;IACzC,OAAO,KAAK,UAAU,GAErB;IAED,OAAO,KAAK,IAAI,GAEf;IAED,8BAA8B;IAC9B,IAAI;IAwBJ,kFAAkF;IAClF,IAAI,CAAC,OAAO,UAAO;YAeL,cAAc;IAO5B,8GAA8G;IAC9G,OAAO,CAAC,KAAK,CAkBX;IAEF,OAAO,CAAC,SAAS,CAEf;IAEF,OAAO,CAAC,MAAM,CAIZ;IAEF,OAAO,CAAC,YAAY,CAQlB;IAEF,OAAO,CAAC,UAAU,CAMhB;IAEF,OAAO,CAAC,eAAe,CAGrB;IAEF,qCAAqC;IACrC,OAAO,CAAC,MAAM;IAUd,OAAO,CAAC,KAAK;IAIb;;;OAGG;IACH,OAAO,CAAC,MAAM,CAWZ;IAEF,+EAA+E;IAC/E,OAAO,CAAC,KAAK;IAqBb,OAAO,CAAC,KAAK;IAOb,OAAO,CAAC,OAAO,CAKb;IAEF,sEAAsE;IACtE,OAAO,CAAC,SAAS,CAIf;IAEF,OAAO,CAAC,KAAK,CAGX;IAEF,OAAO,CAAC,MAAM;IAMd,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,SAAS;IAIjB,sGAAsG;IACtG,OAAO,CAAC,QAAQ;IAQhB,OAAO,CAAC,SAAS;IA4BjB,OAAO,CAAC,YAAY;CA2DrB;AAID,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,aAAa,EAAE,QAAQ,CAAC;KACzB;CACF"}
1
+ {"version":3,"file":"ds-calendar.d.ts","sourceRoot":"","sources":["../../DS1/3-unit/ds-calendar.ts"],"names":[],"mappings":"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"}
@@ -15,7 +15,7 @@ import { html, nothing, unsafeCSS } from "lit";
15
15
  import { define } from "../0-face/define";
16
16
  import { getText } from "../0-face/i18n";
17
17
  import { dateFormatter } from "../0-face/format";
18
- import { addMonths, dateNumber, dayOf, eventsByDay, isDay, isoWeek, monthTitle, monthWeeks, sameMonth, startOfMonth, weekdayNames, } from "../0-face/calendar";
18
+ import { addMonths, dateNumber, dayOf, eventsByDay, heatLevels, isDay, isoWeek, monthTitle, monthWeeks, sameMonth, startOfMonth, weekdayNames, } from "../0-face/calendar";
19
19
  import { CalendarBase, readMonth, readRange } from "./calendar-base";
20
20
  import "../2-core/ds-icon";
21
21
  import metrics from "../1-root/component-metrics.css?inline";
@@ -79,6 +79,12 @@ const openPickers = new Set();
79
79
  * @prop {boolean} fixed - Always draw six weeks, so the month keeps its height.
80
80
  * @prop {boolean} outsideDays - Draw the days of the months before and after
81
81
  * in the title grey (`outside-days`); pressing one moves there.
82
+ * @prop {boolean} heat - Light each day by how much happens on it, as a
83
+ * contribution graph does: the events' `count`s added up (one each by
84
+ * default), in four steps of the busiest day shown.
85
+ * @prop {number} heatMax - The amount that lights a day fully (`heat-max`),
86
+ * for a scale that stays put from month to month; the busiest day shown
87
+ * by default.
82
88
  * @prop {boolean} noHeader - Leave out the title and arrows (`no-header`).
83
89
  * @prop {boolean} noToday - Leave out the Today button (`no-today`).
84
90
  * @prop {string} min - The first day a person may choose.
@@ -118,6 +124,7 @@ export class Calendar extends CalendarBase {
118
124
  super();
119
125
  this.weeksRun = "rows";
120
126
  this.byDay = new Map();
127
+ this.heatByDay = new Map();
121
128
  this.internals = typeof this.attachInternals === "function" ? this.attachInternals() : null;
122
129
  /** The value the field started with, for a form's reset. */
123
130
  this.initial = "";
@@ -280,6 +287,7 @@ export class Calendar extends CalendarBase {
280
287
  this.outsideDays = false;
281
288
  this.noHeader = false;
282
289
  this.noToday = false;
290
+ this.heat = false;
283
291
  this.picker = false;
284
292
  this.time = false;
285
293
  this.required = false;
@@ -416,7 +424,8 @@ export class Calendar extends CalendarBase {
416
424
  const unfolded = this.unfolded === day;
417
425
  const shown = unfolded || events.length <= lines ? events : events.slice(0, Math.max(0, lines - 1));
418
426
  const hidden = events.length - shown.length;
419
- const parts = ["day", isToday ? "today" : "", selected ? "selected" : ""].filter(Boolean).join(" ");
427
+ const heat = this.heatByDay.get(day);
428
+ const parts = ["day", isToday ? "today" : "", selected ? "selected" : "", heat ? "heat" : ""].filter(Boolean).join(" ");
420
429
  return html `<div
421
430
  role="gridcell"
422
431
  class="day"
@@ -434,6 +443,7 @@ export class Calendar extends CalendarBase {
434
443
  ?data-last=${last}
435
444
  ?data-outside=${outside}
436
445
  ?data-busy=${events.length > 0}
446
+ data-heat=${heat ?? nothing}
437
447
  ?data-open=${unfolded}
438
448
  >
439
449
  <span class="date-line" aria-hidden="true">
@@ -460,6 +470,8 @@ export class Calendar extends CalendarBase {
460
470
  const first = this.firstDay;
461
471
  const weeks = monthWeeks(month, first, this.fixed);
462
472
  this.byDay = eventsByDay(this.events ?? [], weeks[0][0], weeks.at(-1)[6]);
473
+ // Heat is measured over the days of the month shown, so the neighbours' days never set its scale.
474
+ this.heatByDay = this.heat ? heatLevels(new Map([...this.byDay].filter(([day]) => sameMonth(day, month))), this.heatMax) : new Map();
463
475
  // A compact month names its days in a letter (one square) or two (wider); a full month in short.
464
476
  const short = this.compact
465
477
  ? this.squaresPerDay < 2
@@ -730,6 +742,8 @@ Calendar.properties = {
730
742
  outsideDays: { type: Boolean, attribute: "outside-days", reflect: true },
731
743
  noHeader: { type: Boolean, attribute: "no-header", reflect: true },
732
744
  noToday: { type: Boolean, attribute: "no-today", reflect: true },
745
+ heat: { type: Boolean, reflect: true },
746
+ heatMax: { type: Number, attribute: "heat-max" },
733
747
  picker: { type: Boolean, reflect: true },
734
748
  name: { type: String, reflect: true },
735
749
  placeholder: { type: String },
@@ -10695,6 +10695,23 @@ function isoWeek(day) {
10695
10695
  const firstThursday = addDays(january4, 3 - (weekdayOf(january4) + 6) % 7);
10696
10696
  return 1 + Math.round((noon(thursday) - noon(firstThursday)) / 6048e5);
10697
10697
  }
10698
+ /**
10699
+ * How strongly each day is lit, as a contribution graph lights it: the
10700
+ * amounts of its events added up, in four steps of the busiest day (or of
10701
+ * `max`, for a scale that stays put from month to month). A day with nothing
10702
+ * has no level.
10703
+ */
10704
+ function heatLevels(byDay, max) {
10705
+ const amounts = /* @__PURE__ */ new Map();
10706
+ for (const [day, events] of byDay) {
10707
+ const amount = events.reduce((sum, event) => sum + (typeof event.count === "number" && Number.isFinite(event.count) ? Math.max(0, event.count) : 1), 0);
10708
+ if (amount > 0) amounts.set(day, amount);
10709
+ }
10710
+ const top = typeof max === "number" && Number.isFinite(max) && max > 0 ? max : Math.max(0, ...amounts.values());
10711
+ const levels = /* @__PURE__ */ new Map();
10712
+ for (const [day, amount] of amounts) levels.set(day, Math.min(4, Math.max(1, Math.ceil(amount / top * 4))));
10713
+ return levels;
10714
+ }
10698
10715
  /** The events that touch each day between `from` and `to`, in order: longest first, then by time. */
10699
10716
  function eventsByDay(events, from, to) {
10700
10717
  const days = /* @__PURE__ */ new Map();
@@ -10928,7 +10945,7 @@ _CalendarBase.properties = {
10928
10945
  };
10929
10946
  //#endregion
10930
10947
  //#region DS1/3-unit/styles/ds-calendar.css?inline
10931
- var ds_calendar_default = "/* ds-calendar.css\n * A month on whole squares. A day is --ds-calendar-day-squares wide (four,\n * one when compact) and, beside its date's square, holds --ds-calendar-lines\n * one-square lines of events and one square to spare. Every line of words\n * stands on the lower sixteen of its square. The boxes are hairlines centred\n * on the grid lines; today is a red disc nine tenths of a square across.\n *\n * A picker is the select's face: one control high at the foot of its square,\n * ten squares wide by default, the calendar icon at its start and the down\n * arrow at its end. Its month opens on the menu's frame, half a square from\n * the field: a 1px line, half a square inside it, no shadow, so eight squares\n * wide. Inside, on whole squares: the first and last day to type (each with\n * its time below when times are on), one square spare, the compact month one\n * square a day, then Include time and Clear, each one square, set off by\n * one-square separators with a line through them. */\n\n:host {\n --ds-calendar-day: calc(var(--ds-unit) * var(--ds-calendar-day-squares, 4));\n --ds-calendar-day-height: calc(var(--ds-unit) * (var(--ds-calendar-lines, 2) + 2));\n --ds-calendar-line: 1px solid var(--ds-border-color);\n --ds-calendar-today: var(--ds-red-for-blood);\n --ds-calendar-today-ink: var(--ds-white, #fff);\n --ds-calendar-selected: var(--ds-sharp-blue);\n --ds-calendar-selected-ink: var(--ds-white, #fff);\n --ds-calendar-band: color-mix(in srgb, var(--ds-calendar-selected) 16%, var(--ds-paper));\n --ds-calendar-mark: calc(var(--ds-unit) * 0.9);\n --ds-calendar-pad: calc(4px * var(--ds-sf, 1));\n box-sizing: border-box;\n display: inline-block;\n vertical-align: top;\n color: var(--ds-text-color);\n font-family: var(--ds-typeface-regular);\n font-feature-settings: var(--ds-font-features);\n font-size: var(--ds-type-size-default);\n font-weight: var(--ds-type-weight-default);\n line-height: var(--ds-type-lineheight-default);\n letter-spacing: var(--ds-type-letterspacing-default);\n}\n:host([hidden]) { display: none !important; }\n/* A compact day is square: one square, or two where the month has room. Its marks grow with it. */\n:host([compact]) {\n --ds-calendar-day-height: var(--ds-calendar-day);\n --ds-calendar-mark: calc(var(--ds-unit) * (var(--ds-calendar-day-squares, 1) * 0.75 + 0.15));\n}\n\n.calendar { width: calc(7 * var(--ds-calendar-day)); }\n\n/* The title and the arrows: one square, one square above the days (none when compact). */\n.header {\n box-sizing: border-box;\n display: flex;\n align-items: flex-end;\n gap: var(--ds-calendar-pad);\n height: var(--ds-unit);\n margin-block-end: var(--ds-unit);\n}\n:host([compact]) .header { margin-block-end: 0; }\n.title {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n font-family: var(--ds-typeface-medium, var(--ds-typeface-regular));\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.controls {\n display: flex;\n flex: 0 0 auto;\n align-items: flex-end;\n gap: 0;\n}\n/* Each arrow is a chevron on its own square, one control high at the square's foot,\n * in the title grey; the pointer fills it with the system's corners and inks it. */\n.nav {\n flex: 0 0 auto;\n width: var(--ds-unit);\n height: var(--ds-control-height);\n border-radius: var(--ds-radius);\n color: var(--ds-title-color);\n}\n.nav:hover:not(:disabled) { background: var(--ds-fill); color: var(--ds-text-color); }\n:host(:dir(rtl)) .nav > ds-icon { transform: scaleX(-1); }\n\n.grid { outline: none; }\n.weekdays,\n.week {\n display: grid;\n grid-template-columns: repeat(7, var(--ds-calendar-day));\n}\n\n/* The weekday names: a block line in the title grey, at the start of a wide day, centred on a compact one. */\n.weekday {\n box-sizing: border-box;\n height: var(--ds-unit);\n padding: calc(4px * var(--ds-sf, 1)) var(--ds-calendar-pad) 0;\n overflow: hidden;\n color: var(--ds-title-color);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n:host([compact]) .weekday { padding-inline: 0; text-align: center; }\n\n/* A day's box: hairlines on the grid lines, top and start; the last column\n * and the last week close it at the end and the foot. */\n.day {\n position: relative;\n box-sizing: border-box;\n min-width: 0;\n height: var(--ds-calendar-day-height);\n outline: none;\n}\n.day::after {\n position: absolute;\n inset: -0.5px;\n border-block-start: var(--ds-calendar-line);\n border-inline-start: var(--ds-calendar-line);\n content: \"\";\n pointer-events: none;\n}\n.day:nth-child(7)::after { border-inline-end: var(--ds-calendar-line); }\n.week:last-child .day::after { border-block-end: var(--ds-calendar-line); }\n:host([compact]) .day::after { content: none; }\n\n.day[data-day] { cursor: default; }\n:host([selectable]) .day[data-day]:not([aria-disabled=\"true\"]) { cursor: pointer; }\n:host([selectable]:not([compact])) .day[data-day]:not([aria-disabled=\"true\"]):hover { background: var(--ds-row-fill-hover, var(--ds-fill)); }\n.day:focus-visible { outline: 2px solid var(--ds-focus-ring); outline-offset: -2px; }\n:host([compact]) .day:focus-visible { outline: none; }\n:host([compact]) .day:focus-visible .date { outline: 2px solid var(--ds-focus-ring); outline-offset: -1px; }\n\n/* The date: on the first square, at the end of a wide day, in the title grey. */\n.date-line {\n box-sizing: border-box;\n display: flex;\n align-items: flex-end;\n justify-content: flex-end;\n gap: var(--ds-calendar-pad);\n height: var(--ds-unit);\n padding-inline: var(--ds-calendar-pad);\n}\n.date {\n color: var(--ds-title-color);\n font-variant-numeric: tabular-nums;\n}\n.week-number {\n margin-inline-end: auto;\n color: var(--ds-title-color);\n font-family: var(--ds-typeface-compressed);\n}\n\n/* A compact day: the date centred in its square, in the ink; with events,\n * the empty days fall back to the title grey and the busy ones keep the ink. */\n:host([compact]) .date-line { justify-content: center; padding: 0; }\n:host([compact]) .date { color: var(--ds-text-color); }\n:host([compact][data-events]) .day:not([data-busy]) .date { color: var(--ds-title-color); }\n:host([compact][selectable]) .day[data-day]:not([aria-disabled=\"true\"], [data-today], [data-selected]):hover .date { background: var(--ds-fill); }\n\n/* Today and the selected day: a disc nine tenths of a square, centred on the date's square. */\n.day:is([data-today], [data-selected]) .date-line { align-items: center; padding-inline-end: calc(var(--ds-unit) * 0.05); }\n:host([compact]) .day:is([data-today], [data-selected]) .date-line { padding: 0; }\n.date {\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: var(--ds-calendar-mark);\n height: var(--ds-calendar-mark);\n border-radius: 999px;\n}\n.day:not([data-today], [data-selected]) .date { min-width: 0; height: auto; }\n:host([compact]) .day .date { min-width: var(--ds-calendar-mark); height: var(--ds-calendar-mark); }\n.day[data-today] .date { background: var(--ds-calendar-today); color: var(--ds-calendar-today-ink); letter-spacing: 0; }\n/* The selected day, and each end of a range: a square with the system's corners in the selection's blue. */\n.day[data-selected] .date { border-radius: var(--ds-radius); background: var(--ds-calendar-selected); color: var(--ds-calendar-selected-ink); letter-spacing: 0; }\n\n.day[aria-disabled=\"true\"] .date { color: var(--ds-title-color); opacity: 0.5; }\n\n/* Events: one square each, stacked edge to edge on the day's lines, drawn one pixel short. */\n.events {\n display: flex;\n flex-direction: column;\n}\n.event,\n.more {\n box-sizing: border-box;\n display: flex;\n align-items: flex-end;\n gap: var(--ds-calendar-pad);\n height: calc(var(--ds-unit) - 1px);\n margin: 0.5px;\n padding-inline: var(--ds-calendar-pad);\n overflow: hidden;\n white-space: nowrap;\n}\n.event {\n background: var(--ds-calendar-event-fill, var(--ds-fill));\n color: var(--ds-calendar-event-ink, var(--ds-text-color));\n cursor: pointer;\n}\n.event .name { min-width: 0; overflow: hidden; text-overflow: ellipsis; }\n.event .time { flex: 0 0 auto; opacity: 0.6; font-variant-numeric: tabular-nums; }\n.more { color: var(--ds-title-color); cursor: pointer; }\n.more:hover { color: var(--ds-text-color); }\n\n/* A day whose hidden events are unfolded lays them over the days below. */\n.day[data-open] { z-index: 1; }\n.day[data-open] .events {\n position: absolute;\n inset-block-start: var(--ds-unit);\n inset-inline: 0;\n background: var(--ds-paper);\n box-shadow: 0 0 0 1px var(--ds-border-color);\n}\n\n@media (prefers-reduced-motion: no-preference) {\n .event, .more { transition: opacity 120ms ease; }\n}\n\n/* A compact day's date stands in the middle of its square once the square is two or more wide. */\n:host([compact][data-roomy]) .date-line { align-items: center; height: var(--ds-calendar-day-height); }\n\n/* The days of the months before and after, in the title grey. */\n.day[data-outside] .date { color: var(--ds-title-color); }\n:host([compact]) .day[data-outside] .date { color: var(--ds-title-color); }\n\n/* A range: the days between its ends on a band of the selection's blue; the band runs into each end's square. */\n.day[data-inside] { background: var(--ds-calendar-band); }\n:host(:not([compact])) .day:is([data-first], [data-last]) { background: var(--ds-calendar-band); }\n:host([compact]) .day[data-first] { background: linear-gradient(to right, transparent 50%, var(--ds-calendar-band) 50%); }\n:host([compact]) .day[data-last] { background: linear-gradient(to left, transparent 50%, var(--ds-calendar-band) 50%); }\n:host([compact]:dir(rtl)) .day[data-first] { background: linear-gradient(to left, transparent 50%, var(--ds-calendar-band) 50%); }\n:host([compact]:dir(rtl)) .day[data-last] { background: linear-gradient(to right, transparent 50%, var(--ds-calendar-band) 50%); }\n:host([compact][selectable]) .day[data-inside]:hover .date { background: transparent; }\n\n/* Today, as words in the title grey beside the arrows. */\n.today-button {\n height: var(--ds-control-height);\n padding-inline: calc(4px * var(--ds-sf, 1));\n border: 0;\n background: transparent;\n color: var(--ds-title-color);\n}\n.today-button:hover { background: var(--ds-fill); color: var(--ds-text-color); }\n\n/* In a compact month the pointer's day and the chosen day take squares with the system's corners; today keeps its disc. */\n:host([compact]) .date { border-radius: var(--ds-radius); }\n:host([compact]) .day[data-today]:not([data-selected]) .date { border-radius: 999px; }\n\n/* The picker: a field that opens the small month. */\n:host([picker]) {\n --ds-calendar-picker-width: calc(10 * var(--ds-unit));\n display: inline-flex;\n align-items: flex-end;\n vertical-align: bottom;\n height: var(--ds-unit);\n}\n\n.trigger {\n display: inline-flex;\n align-items: flex-end;\n gap: calc(4px * var(--ds-sf, 1));\n width: var(--ds-calendar-picker-width);\n padding-inline: 0 calc(4px * var(--ds-sf, 1));\n text-align: start;\n cursor: pointer;\n}\n.trigger:disabled { cursor: default; }\n.trigger .icon,\n.trigger .arrow { flex: 0 0 auto; }\n.trigger .text {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.trigger[data-empty] .text { color: var(--ds-title-color); }\n.trigger[aria-expanded=\"true\"] .arrow { transform: rotate(180deg); }\n\n/* The month's panel, on the menu's frame. */\n.popover {\n position: fixed;\n inset: auto;\n box-sizing: border-box;\n margin: 0;\n padding: calc(var(--ds-unit) / 2 - 1px);\n border: 1px solid var(--ds-border-color);\n border-radius: var(--ds-radius-card);\n background: var(--ds-paper);\n color: var(--ds-text-color);\n box-shadow: none;\n overflow: visible;\n}\n.popover:not(:popover-open) { display: none; }\n\n/* The days to type: a row of one square for each end, its time on the row below; one square spare after. */\n.fields { margin-block-end: var(--ds-unit); }\n.field-row {\n display: flex;\n align-items: flex-end;\n gap: calc(var(--ds-unit) / 2);\n height: var(--ds-unit);\n}\n.field { flex: 1 1 auto; min-width: 0; }\n.clock { flex: 1 1 auto; min-width: 0; }\n.fields, .options { width: calc(7 * var(--ds-unit)); }\n\n/* Include time and Clear, each one square, the words on the sixteen. */\n.option {\n box-sizing: border-box;\n display: flex;\n align-items: flex-end;\n justify-content: space-between;\n gap: var(--ds-unit);\n width: 100%;\n height: var(--ds-unit);\n margin: 0;\n padding: 0 calc(4px * var(--ds-sf, 1));\n border: 0;\n border-radius: var(--ds-radius);\n background: transparent;\n color: var(--ds-text-color);\n font: inherit;\n letter-spacing: inherit;\n text-align: start;\n cursor: pointer;\n}\n.option:hover:not(:disabled) { background: var(--ds-row-fill-hover, var(--ds-fill)); }\n.option:focus-visible { outline: 2px solid var(--ds-focus-ring); outline-offset: -2px; }\n.option:disabled { color: var(--ds-title-color); cursor: default; }\n.option .ds-switch { flex: 0 0 auto; }\n\n/* A separator: one square with a 1px line through its middle. */\n.separator {\n position: relative;\n height: var(--ds-unit);\n}\n.separator::after {\n position: absolute;\n inset-inline: 0;\n top: calc(50% - 0.5px);\n height: 1px;\n background: var(--ds-border-color);\n content: \"\";\n}\n\n@media (prefers-reduced-motion: no-preference) {\n .popover:popover-open {\n animation: ds-calendar-picker-in 140ms cubic-bezier(0.22, 1, 0.36, 1);\n }\n .trigger .arrow { transition: transform 140ms ease; }\n}\n@keyframes ds-calendar-picker-in {\n from { opacity: 0; transform: translateY(calc(-4px * var(--ds-sf, 1))); }\n to { opacity: 1; transform: none; }\n}\n";
10948
+ var ds_calendar_default = "/* ds-calendar.css\n * A month on whole squares. A day is --ds-calendar-day-squares wide (four,\n * one when compact) and, beside its date's square, holds --ds-calendar-lines\n * one-square lines of events and one square to spare. Every line of words\n * stands on the lower sixteen of its square. The boxes are hairlines centred\n * on the grid lines; today is a red disc nine tenths of a square across.\n *\n * A picker is the select's face: one control high at the foot of its square,\n * ten squares wide by default, the calendar icon at its start and the down\n * arrow at its end. Its month opens on the menu's frame, half a square from\n * the field: a 1px line, half a square inside it, no shadow, so eight squares\n * wide. Inside, on whole squares: the first and last day to type (each with\n * its time below when times are on), one square spare, the compact month one\n * square a day, then Include time and Clear, each one square, set off by\n * one-square separators with a line through them. */\n\n:host {\n --ds-calendar-day: calc(var(--ds-unit) * var(--ds-calendar-day-squares, 4));\n --ds-calendar-day-height: calc(var(--ds-unit) * (var(--ds-calendar-lines, 2) + 2));\n --ds-calendar-line: 1px solid var(--ds-border-color);\n --ds-calendar-today: var(--ds-red-for-blood);\n --ds-calendar-today-ink: var(--ds-white, #fff);\n --ds-calendar-selected: var(--ds-sharp-blue);\n --ds-calendar-selected-ink: var(--ds-white, #fff);\n --ds-calendar-band: color-mix(in srgb, var(--ds-calendar-selected) 16%, var(--ds-paper));\n --ds-calendar-mark: calc(var(--ds-unit) * 0.9);\n --ds-calendar-pad: calc(4px * var(--ds-sf, 1));\n /* Heat, in four steps of the accent: its pastel, between, the colour, and\n past it (deepened with the ink on light paper, the full colour on dark).\n The words are the ink on every step but the two darkest on dark paper. */\n --ds-calendar-heat-1: var(--one-apple-green-pastel, color-mix(in srgb, var(--ds-apple-green) 40%, var(--ds-paper)));\n --ds-calendar-heat-3: var(--one-apple-green, var(--ds-apple-green));\n --ds-calendar-heat-2: color-mix(in srgb, var(--ds-calendar-heat-1), var(--ds-calendar-heat-3));\n --ds-calendar-heat-4: light-dark(color-mix(in srgb, var(--ds-apple-green) 60%, var(--ds-slate)), var(--ds-apple-green));\n --ds-calendar-heat-ink-1: light-dark(var(--ds-slate), var(--ds-white));\n --ds-calendar-heat-ink-2: light-dark(var(--ds-slate), var(--ds-white));\n --ds-calendar-heat-ink-3: var(--ds-slate);\n --ds-calendar-heat-ink-4: var(--ds-slate);\n box-sizing: border-box;\n display: inline-block;\n vertical-align: top;\n color: var(--ds-text-color);\n font-family: var(--ds-typeface-regular);\n font-feature-settings: var(--ds-font-features);\n font-size: var(--ds-type-size-default);\n font-weight: var(--ds-type-weight-default);\n line-height: var(--ds-type-lineheight-default);\n letter-spacing: var(--ds-type-letterspacing-default);\n}\n:host([hidden]) { display: none !important; }\n/* A compact day is square: one square, or two where the month has room. Its marks grow with it. */\n:host([compact]) {\n --ds-calendar-day-height: var(--ds-calendar-day);\n --ds-calendar-mark: calc(var(--ds-unit) * (var(--ds-calendar-day-squares, 1) * 0.75 + 0.15));\n}\n\n.calendar { width: calc(7 * var(--ds-calendar-day)); }\n\n/* The title and the arrows: one square, one square above the days (none when compact). */\n.header {\n box-sizing: border-box;\n display: flex;\n align-items: flex-end;\n gap: var(--ds-calendar-pad);\n height: var(--ds-unit);\n margin-block-end: var(--ds-unit);\n}\n:host([compact]) .header { margin-block-end: 0; }\n.title {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n font-family: var(--ds-typeface-medium, var(--ds-typeface-regular));\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.controls {\n display: flex;\n flex: 0 0 auto;\n align-items: flex-end;\n gap: 0;\n}\n/* Each arrow is a chevron on its own square, one control high at the square's foot,\n * in the title grey; the pointer fills it with the system's corners and inks it. */\n.nav {\n flex: 0 0 auto;\n width: var(--ds-unit);\n height: var(--ds-control-height);\n border-radius: var(--ds-radius);\n color: var(--ds-title-color);\n}\n.nav:hover:not(:disabled) { background: var(--ds-fill); color: var(--ds-text-color); }\n:host(:dir(rtl)) .nav > ds-icon { transform: scaleX(-1); }\n\n.grid { outline: none; }\n.weekdays,\n.week {\n display: grid;\n grid-template-columns: repeat(7, var(--ds-calendar-day));\n}\n\n/* The weekday names: a block line in the title grey, at the start of a wide day, centred on a compact one. */\n.weekday {\n box-sizing: border-box;\n height: var(--ds-unit);\n padding: calc(4px * var(--ds-sf, 1)) var(--ds-calendar-pad) 0;\n overflow: hidden;\n color: var(--ds-title-color);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n:host([compact]) .weekday { padding-inline: 0; text-align: center; }\n\n/* A day's box: hairlines on the grid lines, top and start; the last column\n * and the last week close it at the end and the foot. */\n.day {\n position: relative;\n box-sizing: border-box;\n min-width: 0;\n height: var(--ds-calendar-day-height);\n outline: none;\n}\n.day::after {\n position: absolute;\n inset: -0.5px;\n border-block-start: var(--ds-calendar-line);\n border-inline-start: var(--ds-calendar-line);\n content: \"\";\n pointer-events: none;\n}\n.day:nth-child(7)::after { border-inline-end: var(--ds-calendar-line); }\n.week:last-child .day::after { border-block-end: var(--ds-calendar-line); }\n:host([compact]) .day::after { content: none; }\n\n.day[data-day] { cursor: default; }\n:host([selectable]) .day[data-day]:not([aria-disabled=\"true\"]) { cursor: pointer; }\n:host([selectable]:not([compact])) .day[data-day]:not([aria-disabled=\"true\"]):hover { background: var(--ds-row-fill-hover, var(--ds-fill)); }\n.day:focus-visible { outline: 2px solid var(--ds-focus-ring); outline-offset: -2px; }\n:host([compact]) .day:focus-visible { outline: none; }\n:host([compact]) .day:focus-visible .date { outline: 2px solid var(--ds-focus-ring); outline-offset: -1px; }\n\n/* The date: on the first square, at the end of a wide day, in the title grey. */\n.date-line {\n box-sizing: border-box;\n display: flex;\n align-items: flex-end;\n justify-content: flex-end;\n gap: var(--ds-calendar-pad);\n height: var(--ds-unit);\n padding-inline: var(--ds-calendar-pad);\n}\n.date {\n color: var(--ds-title-color);\n font-variant-numeric: tabular-nums;\n}\n.week-number {\n margin-inline-end: auto;\n color: var(--ds-title-color);\n font-family: var(--ds-typeface-compressed);\n}\n\n/* A compact day: the date centred in its square, in the ink; with events,\n * the empty days fall back to the title grey and the busy ones keep the ink. */\n:host([compact]) .date-line { justify-content: center; padding: 0; }\n:host([compact]) .date { color: var(--ds-text-color); }\n:host([compact][data-events]) .day:not([data-busy]) .date { color: var(--ds-title-color); }\n:host([compact][selectable]) .day[data-day]:not([aria-disabled=\"true\"], [data-today], [data-selected]):hover .date { background: var(--ds-fill); }\n\n/* Today and the selected day: a disc nine tenths of a square, centred on the date's square. */\n.day:is([data-today], [data-selected]) .date-line { align-items: center; padding-inline-end: calc(var(--ds-unit) * 0.05); }\n:host([compact]) .day:is([data-today], [data-selected]) .date-line { padding: 0; }\n.date {\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: var(--ds-calendar-mark);\n height: var(--ds-calendar-mark);\n border-radius: 999px;\n}\n.day:not([data-today], [data-selected]) .date { min-width: 0; height: auto; }\n:host([compact]) .day .date { min-width: var(--ds-calendar-mark); height: var(--ds-calendar-mark); }\n.day[data-today] .date { background: var(--ds-calendar-today); color: var(--ds-calendar-today-ink); letter-spacing: 0; }\n/* The selected day, and each end of a range: a square with the system's corners in the selection's blue. */\n.day[data-selected] .date { border-radius: var(--ds-radius); background: var(--ds-calendar-selected); color: var(--ds-calendar-selected-ink); letter-spacing: 0; }\n\n.day[aria-disabled=\"true\"] .date { color: var(--ds-title-color); opacity: 0.5; }\n\n/* Heat: the date's mark filled by its step, a picture's corners, inside the day's square; today and the selection draw over it. */\n.day[data-heat]:not([data-today], [data-selected]) .date-line { align-items: center; padding-inline-end: calc(var(--ds-unit) * 0.05); }\n.day[data-heat]:not([data-today], [data-selected]) .date {\n min-width: var(--ds-calendar-mark);\n height: var(--ds-calendar-mark);\n border-radius: var(--ds-radius);\n letter-spacing: 0;\n}\n.day[data-heat=\"1\"]:not([data-today], [data-selected]) .date { background: var(--ds-calendar-heat-1); color: var(--ds-calendar-heat-ink-1); }\n.day[data-heat=\"2\"]:not([data-today], [data-selected]) .date { background: var(--ds-calendar-heat-2); color: var(--ds-calendar-heat-ink-2); }\n.day[data-heat=\"3\"]:not([data-today], [data-selected]) .date { background: var(--ds-calendar-heat-3); color: var(--ds-calendar-heat-ink-3); }\n.day[data-heat=\"4\"]:not([data-today], [data-selected]) .date { background: var(--ds-calendar-heat-4); color: var(--ds-calendar-heat-ink-4); }\n:host([compact]) .day[data-heat]:not([data-today], [data-selected]) .date-line { padding: 0; }\n\n/* Events: one square each, stacked edge to edge on the day's lines, drawn one pixel short. */\n.events {\n display: flex;\n flex-direction: column;\n}\n.event,\n.more {\n box-sizing: border-box;\n display: flex;\n align-items: flex-end;\n gap: var(--ds-calendar-pad);\n height: calc(var(--ds-unit) - 1px);\n margin: 0.5px;\n padding-inline: var(--ds-calendar-pad);\n overflow: hidden;\n white-space: nowrap;\n}\n.event {\n background: var(--ds-calendar-event-fill, var(--ds-fill));\n color: var(--ds-calendar-event-ink, var(--ds-text-color));\n cursor: pointer;\n}\n.event .name { min-width: 0; overflow: hidden; text-overflow: ellipsis; }\n.event .time { flex: 0 0 auto; opacity: 0.6; font-variant-numeric: tabular-nums; }\n.more { color: var(--ds-title-color); cursor: pointer; }\n.more:hover { color: var(--ds-text-color); }\n\n/* A day whose hidden events are unfolded lays them over the days below. */\n.day[data-open] { z-index: 1; }\n.day[data-open] .events {\n position: absolute;\n inset-block-start: var(--ds-unit);\n inset-inline: 0;\n background: var(--ds-paper);\n box-shadow: 0 0 0 1px var(--ds-border-color);\n}\n\n@media (prefers-reduced-motion: no-preference) {\n .event, .more { transition: opacity 120ms ease; }\n}\n\n/* A compact day's date stands in the middle of its square once the square is two or more wide. */\n:host([compact][data-roomy]) .date-line { align-items: center; height: var(--ds-calendar-day-height); }\n\n/* The days of the months before and after, in the title grey. */\n.day[data-outside] .date { color: var(--ds-title-color); }\n:host([compact]) .day[data-outside] .date { color: var(--ds-title-color); }\n\n/* A range: the days between its ends on a band of the selection's blue; the band runs into each end's square. */\n.day[data-inside] { background: var(--ds-calendar-band); }\n:host(:not([compact])) .day:is([data-first], [data-last]) { background: var(--ds-calendar-band); }\n:host([compact]) .day[data-first] { background: linear-gradient(to right, transparent 50%, var(--ds-calendar-band) 50%); }\n:host([compact]) .day[data-last] { background: linear-gradient(to left, transparent 50%, var(--ds-calendar-band) 50%); }\n:host([compact]:dir(rtl)) .day[data-first] { background: linear-gradient(to left, transparent 50%, var(--ds-calendar-band) 50%); }\n:host([compact]:dir(rtl)) .day[data-last] { background: linear-gradient(to right, transparent 50%, var(--ds-calendar-band) 50%); }\n:host([compact][selectable]) .day[data-inside]:hover .date { background: transparent; }\n\n/* Today, as words in the title grey beside the arrows. */\n.today-button {\n height: var(--ds-control-height);\n padding-inline: calc(4px * var(--ds-sf, 1));\n border: 0;\n background: transparent;\n color: var(--ds-title-color);\n}\n.today-button:hover { background: var(--ds-fill); color: var(--ds-text-color); }\n\n/* In a compact month the pointer's day and the chosen day take squares with the system's corners; today keeps its disc. */\n:host([compact]) .date { border-radius: var(--ds-radius); }\n:host([compact]) .day[data-today]:not([data-selected]) .date { border-radius: 999px; }\n\n/* The picker: a field that opens the small month. */\n:host([picker]) {\n --ds-calendar-picker-width: calc(10 * var(--ds-unit));\n display: inline-flex;\n align-items: flex-end;\n vertical-align: bottom;\n height: var(--ds-unit);\n}\n\n.trigger {\n display: inline-flex;\n align-items: flex-end;\n gap: calc(4px * var(--ds-sf, 1));\n width: var(--ds-calendar-picker-width);\n padding-inline: 0 calc(4px * var(--ds-sf, 1));\n text-align: start;\n cursor: pointer;\n}\n.trigger:disabled { cursor: default; }\n.trigger .icon,\n.trigger .arrow { flex: 0 0 auto; }\n.trigger .text {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.trigger[data-empty] .text { color: var(--ds-title-color); }\n.trigger[aria-expanded=\"true\"] .arrow { transform: rotate(180deg); }\n\n/* The month's panel, on the menu's frame. */\n.popover {\n position: fixed;\n inset: auto;\n box-sizing: border-box;\n margin: 0;\n padding: calc(var(--ds-unit) / 2 - 1px);\n border: 1px solid var(--ds-border-color);\n border-radius: var(--ds-radius-card);\n background: var(--ds-paper);\n color: var(--ds-text-color);\n box-shadow: none;\n overflow: visible;\n}\n.popover:not(:popover-open) { display: none; }\n\n/* The days to type: a row of one square for each end, its time on the row below; one square spare after. */\n.fields { margin-block-end: var(--ds-unit); }\n.field-row {\n display: flex;\n align-items: flex-end;\n gap: calc(var(--ds-unit) / 2);\n height: var(--ds-unit);\n}\n.field { flex: 1 1 auto; min-width: 0; }\n.clock { flex: 1 1 auto; min-width: 0; }\n.fields, .options { width: calc(7 * var(--ds-unit)); }\n\n/* Include time and Clear, each one square, the words on the sixteen. */\n.option {\n box-sizing: border-box;\n display: flex;\n align-items: flex-end;\n justify-content: space-between;\n gap: var(--ds-unit);\n width: 100%;\n height: var(--ds-unit);\n margin: 0;\n padding: 0 calc(4px * var(--ds-sf, 1));\n border: 0;\n border-radius: var(--ds-radius);\n background: transparent;\n color: var(--ds-text-color);\n font: inherit;\n letter-spacing: inherit;\n text-align: start;\n cursor: pointer;\n}\n.option:hover:not(:disabled) { background: var(--ds-row-fill-hover, var(--ds-fill)); }\n.option:focus-visible { outline: 2px solid var(--ds-focus-ring); outline-offset: -2px; }\n.option:disabled { color: var(--ds-title-color); cursor: default; }\n.option .ds-switch { flex: 0 0 auto; }\n\n/* A separator: one square with a 1px line through its middle. */\n.separator {\n position: relative;\n height: var(--ds-unit);\n}\n.separator::after {\n position: absolute;\n inset-inline: 0;\n top: calc(50% - 0.5px);\n height: 1px;\n background: var(--ds-border-color);\n content: \"\";\n}\n\n@media (prefers-reduced-motion: no-preference) {\n .popover:popover-open {\n animation: ds-calendar-picker-in 140ms cubic-bezier(0.22, 1, 0.36, 1);\n }\n .trigger .arrow { transition: transform 140ms ease; }\n}\n@keyframes ds-calendar-picker-in {\n from { opacity: 0; transform: translateY(calc(-4px * var(--ds-sf, 1))); }\n to { opacity: 1; transform: none; }\n}\n";
10932
10949
  //#endregion
10933
10950
  //#region DS1/3-unit/ds-calendar.ts
10934
10951
  var _Calendar;
@@ -10991,6 +11008,12 @@ var openPickers = /* @__PURE__ */ new Set();
10991
11008
  * @prop {boolean} fixed - Always draw six weeks, so the month keeps its height.
10992
11009
  * @prop {boolean} outsideDays - Draw the days of the months before and after
10993
11010
  * in the title grey (`outside-days`); pressing one moves there.
11011
+ * @prop {boolean} heat - Light each day by how much happens on it, as a
11012
+ * contribution graph does: the events' `count`s added up (one each by
11013
+ * default), in four steps of the busiest day shown.
11014
+ * @prop {number} heatMax - The amount that lights a day fully (`heat-max`),
11015
+ * for a scale that stays put from month to month; the busiest day shown
11016
+ * by default.
10994
11017
  * @prop {boolean} noHeader - Leave out the title and arrows (`no-header`).
10995
11018
  * @prop {boolean} noToday - Leave out the Today button (`no-today`).
10996
11019
  * @prop {string} min - The first day a person may choose.
@@ -11030,6 +11053,7 @@ var Calendar = class extends CalendarBase {
11030
11053
  super();
11031
11054
  this.weeksRun = "rows";
11032
11055
  this.byDay = /* @__PURE__ */ new Map();
11056
+ this.heatByDay = /* @__PURE__ */ new Map();
11033
11057
  this.internals = typeof this.attachInternals === "function" ? this.attachInternals() : null;
11034
11058
  this.initial = "";
11035
11059
  this.shown = false;
@@ -11169,6 +11193,7 @@ var Calendar = class extends CalendarBase {
11169
11193
  this.outsideDays = false;
11170
11194
  this.noHeader = false;
11171
11195
  this.noToday = false;
11196
+ this.heat = false;
11172
11197
  this.picker = false;
11173
11198
  this.time = false;
11174
11199
  this.required = false;
@@ -11291,13 +11316,15 @@ var Calendar = class extends CalendarBase {
11291
11316
  const unfolded = this.unfolded === day;
11292
11317
  const shown = unfolded || events.length <= lines ? events : events.slice(0, Math.max(0, lines - 1));
11293
11318
  const hidden = events.length - shown.length;
11319
+ const heat = this.heatByDay.get(day);
11294
11320
  return b$1`<div
11295
11321
  role="gridcell"
11296
11322
  class="day"
11297
11323
  part=${[
11298
11324
  "day",
11299
11325
  isToday ? "today" : "",
11300
- selected ? "selected" : ""
11326
+ selected ? "selected" : "",
11327
+ heat ? "heat" : ""
11301
11328
  ].filter(Boolean).join(" ")}
11302
11329
  data-day=${day}
11303
11330
  tabindex=${!outside && day === this.tabDay ? 0 : -1}
@@ -11312,6 +11339,7 @@ var Calendar = class extends CalendarBase {
11312
11339
  ?data-last=${last}
11313
11340
  ?data-outside=${outside}
11314
11341
  ?data-busy=${events.length > 0}
11342
+ data-heat=${heat ?? A}
11315
11343
  ?data-open=${unfolded}
11316
11344
  >
11317
11345
  <span class="date-line" aria-hidden="true">
@@ -11335,6 +11363,7 @@ var Calendar = class extends CalendarBase {
11335
11363
  const first = this.firstDay;
11336
11364
  const weeks = monthWeeks(month, first, this.fixed);
11337
11365
  this.byDay = eventsByDay(this.events ?? [], weeks[0][0], weeks.at(-1)[6]);
11366
+ this.heatByDay = this.heat ? heatLevels(new Map([...this.byDay].filter(([day]) => sameMonth(day, month))), this.heatMax) : /* @__PURE__ */ new Map();
11338
11367
  const short = this.compact ? this.squaresPerDay < 2 ? weekdayNames(first, "narrow") : weekdayNames(first, "short").map((name) => /^\p{Script=Latin}/u.test(name) ? name.replace(/\.$/, "").slice(0, 2) : name) : weekdayNames(first, "short");
11339
11368
  const long = weekdayNames(first, "long");
11340
11369
  return b$1`<div class="calendar" part="calendar" @focusout=${this.onFocusOut} @pointerover=${this.onPointerOver} @pointerleave=${() => this.hoverDay = void 0}>
@@ -11637,6 +11666,14 @@ _Calendar.properties = {
11637
11666
  attribute: "no-today",
11638
11667
  reflect: true
11639
11668
  },
11669
+ heat: {
11670
+ type: Boolean,
11671
+ reflect: true
11672
+ },
11673
+ heatMax: {
11674
+ type: Number,
11675
+ attribute: "heat-max"
11676
+ },
11640
11677
  picker: {
11641
11678
  type: Boolean,
11642
11679
  reflect: true