fold-ng 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import { DOCUMENT, NgTemplateOutlet, NgClass, DatePipe } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
- import { input, inject, ElementRef, effect, Directive, Injectable, signal, DestroyRef, model, computed, HostListener, Component, InjectionToken, Service, booleanAttribute, linkedSignal, numberAttribute, ViewEncapsulation, output, viewChild, afterRenderEffect, afterNextRender, contentChildren, TemplateRef, isDevMode, contentChild, Renderer2, RendererStyleFlags2, viewChildren, ViewContainerRef, Injector } from '@angular/core';
3
+ import { input, inject, ElementRef, effect, Directive, Injectable, signal, DestroyRef, model, computed, HostListener, Component, InjectionToken, Service, booleanAttribute, linkedSignal, numberAttribute, ViewEncapsulation, output, viewChild, afterRenderEffect, afterNextRender, contentChildren, TemplateRef, isDevMode, contentChild, untracked, Injector, Renderer2, RendererStyleFlags2, viewChildren, ViewContainerRef } from '@angular/core';
4
4
  import { RouterLink, RouterLinkActive } from '@angular/router';
5
5
 
6
6
  /**
@@ -4248,6 +4248,2292 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
4248
4248
  }]
4249
4249
  }], propDecorators: { sticky: [{ type: i0.Input, args: [{ isSignal: true, alias: "sticky", required: false }] }], stickyOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "stickyOffset", required: false }] }] } });
4250
4250
 
4251
+ /**
4252
+ * Marks an `<ng-template>` as the renderer for every event chip inside a
4253
+ * calendar view, replacing the built-in icon + label + count row:
4254
+ *
4255
+ * ```html
4256
+ * <fold-calendar-month [(month)]="month" [events]="events()">
4257
+ * <ng-template foldCalendarEvent let-event let-band="band">
4258
+ * <fold-avatar [name]="event.data.owner" size="xs" />
4259
+ * <span>{{ event.label }}</span>
4260
+ * </ng-template>
4261
+ * </fold-calendar-month>
4262
+ * ```
4263
+ *
4264
+ * `let-event` and `let-band` are **fully typed** under `strictTemplates`: the
4265
+ * directive is generic over the same `T` as the events it renders, and its
4266
+ * context guard carries that through, so `event.data` is the app's own record
4267
+ * with no cast.
4268
+ *
4269
+ * @selector `ng-template[foldCalendarEvent]`
4270
+ */
4271
+ class FoldCalendarEventDirective {
4272
+ template = inject(TemplateRef);
4273
+ /** Teaches the template checker what `let-event` and `let-band` are. */
4274
+ static ngTemplateContextGuard(_directive, context) {
4275
+ // Compile-time only — Angular never calls this; the narrowing is the point.
4276
+ return context !== undefined;
4277
+ }
4278
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCalendarEventDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4279
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.8", type: FoldCalendarEventDirective, isStandalone: true, selector: "ng-template[foldCalendarEvent]", ngImport: i0 });
4280
+ }
4281
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCalendarEventDirective, decorators: [{
4282
+ type: Directive,
4283
+ args: [{
4284
+ selector: "ng-template[foldCalendarEvent]",
4285
+ standalone: true,
4286
+ }]
4287
+ }] });
4288
+
4289
+ /**
4290
+ * Plain-date arithmetic for the calendar family — `YYYY-MM-DD` in, `YYYY-MM-DD`
4291
+ * out. Framework-free and side-effect-free, so every rule here is unit-testable
4292
+ * without a TestBed.
4293
+ *
4294
+ * ## Why a string and not a `Date`
4295
+ * A calendar of all-day spans deals in **plain dates** — "the 18th of May",
4296
+ * with no instant and no zone. A `Date` is a timestamp, so the moment one
4297
+ * crosses a zone or a DST boundary the same span lands on a different cell:
4298
+ * `new Date("2026-05-18")` is UTC midnight, which is the *17th* anywhere west
4299
+ * of Greenwich. Modelling the domain as `YYYY-MM-DD` removes that entire bug
4300
+ * class by construction, and buys three things for free: values compare
4301
+ * **lexicographically** (`a <= b` *is* "on or before"), they are `===`-equal
4302
+ * when they mean the same day (a `Date` never is), and they are already the
4303
+ * wire format every backend sends.
4304
+ *
4305
+ * {@link FoldTimelineNode} keeps a native `Date` on purpose — it plots dated
4306
+ * *instants* on a rail, which is the other domain.
4307
+ *
4308
+ * Arithmetic runs through `Date.UTC`, never local time: UTC has no DST, so a
4309
+ * day is always exactly 86 400 000 ms and "add 1 day" can never silently
4310
+ * return the same date twice (or skip one) around a clock change.
4311
+ *
4312
+ * ## Supported range
4313
+ * `0001-01-01` to `9999-12-31`. The four-digit year is what makes the
4314
+ * lexicographic guarantee hold, so it is enforced rather than assumed: years
4315
+ * below 1000 are zero-padded (`0099-12-31`, which still sorts correctly), and
4316
+ * arithmetic that walks past `9999-12-31` returns a five-digit string that
4317
+ * {@link foldIsCalendarDate} rejects — a value that fails the type's own
4318
+ * predicate rather than one that silently sorts before every other date.
4319
+ */
4320
+ /** Milliseconds in a UTC day — exact, since UTC has no DST. */
4321
+ const MS_PER_DAY = 86_400_000;
4322
+ /** `FoldWeekday` → the index `Date.prototype.getUTCDay()` returns for it. */
4323
+ const WEEKDAY_INDEX = {
4324
+ sun: 0,
4325
+ mon: 1,
4326
+ tue: 2,
4327
+ wed: 3,
4328
+ thu: 4,
4329
+ fri: 5,
4330
+ sat: 6,
4331
+ };
4332
+ /** `getUTCDay()` index → the `FoldWeekday` it stands for. */
4333
+ const WEEKDAY_AT = [
4334
+ "sun",
4335
+ "mon",
4336
+ "tue",
4337
+ "wed",
4338
+ "thu",
4339
+ "fri",
4340
+ "sat",
4341
+ ];
4342
+ /**
4343
+ * The days a calendar shades as the weekend by default. A separate fact from
4344
+ * the week's anchor day: a Sunday-first calendar still rests on Sat/Sun, and a
4345
+ * Saturday-first one (much of the Middle East) rests on Fri/Sat while starting
4346
+ * on Saturday — which is why the two are different inputs, not one derived
4347
+ * from the other.
4348
+ */
4349
+ const FOLD_DEFAULT_WEEKEND_DAYS = [
4350
+ "sat",
4351
+ "sun",
4352
+ ];
4353
+ /** `YYYY-MM-DD`, structurally. Rejects `2026-5-1`, `2026-05-1`, junk suffixes. */
4354
+ const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
4355
+ /** Left-pads a 1–2 digit date part to the two digits the format requires. */
4356
+ function pad2(value) {
4357
+ return value < 10 ? `0${value}` : `${value}`;
4358
+ }
4359
+ /**
4360
+ * Left-pads a year to four digits. Year 99 has to render as `0099`, not `99`,
4361
+ * or it sorts before every other date and breaks the whole comparison model.
4362
+ */
4363
+ function pad4(value) {
4364
+ if (value >= 1000) {
4365
+ return `${value}`;
4366
+ }
4367
+ if (value >= 100) {
4368
+ return `0${value}`;
4369
+ }
4370
+ return value >= 10 ? `00${value}` : `000${value}`;
4371
+ }
4372
+ /**
4373
+ * Whether `value` is a well-formed **and real** plain date. Catches both a
4374
+ * malformed shape (`"2026-5-1"`) and an impossible day (`"2026-02-30"`), the
4375
+ * latter by round-tripping through UTC and checking the parts survived.
4376
+ */
4377
+ function foldIsCalendarDate(value) {
4378
+ if (!DATE_PATTERN.test(value)) {
4379
+ return false;
4380
+ }
4381
+ const year = Number(value.slice(0, 4));
4382
+ const month = Number(value.slice(5, 7));
4383
+ const day = Number(value.slice(8, 10));
4384
+ if (month < 1 || month > 12 || day < 1 || day > 31) {
4385
+ return false;
4386
+ }
4387
+ const parsed = new Date(Date.UTC(year, month - 1, day));
4388
+ return parsed.getUTCMonth() === month - 1 && parsed.getUTCDate() === day;
4389
+ }
4390
+ /**
4391
+ * Epoch milliseconds at UTC midnight of `date` — the internal representation
4392
+ * every computation here runs on. A malformed date yields `NaN`, which
4393
+ * propagates visibly rather than silently shifting a result.
4394
+ */
4395
+ function toUtcMs(date) {
4396
+ const year = Number(date.slice(0, 4));
4397
+ const month = Number(date.slice(5, 7));
4398
+ const day = Number(date.slice(8, 10));
4399
+ const at = new Date(Date.UTC(year, month - 1, day));
4400
+ // `Date.UTC` maps years 0–99 onto 1900–1999; only `setUTCFullYear` undoes it.
4401
+ if (year < 100) {
4402
+ at.setUTCFullYear(year, month - 1, day);
4403
+ }
4404
+ return at.getTime();
4405
+ }
4406
+ /** UTC midnight milliseconds back to `YYYY-MM-DD`. */
4407
+ function fromUtcMs(ms) {
4408
+ const at = new Date(ms);
4409
+ return `${pad4(at.getUTCFullYear())}-${pad2(at.getUTCMonth() + 1)}-${pad2(at.getUTCDate())}`;
4410
+ }
4411
+ /**
4412
+ * `date` as a whole number of days since the epoch.
4413
+ *
4414
+ * The internal currency of anything that sorts, compares or clips a lot of
4415
+ * dates. Strings are the right **model** — they are the wire format and they
4416
+ * cannot carry a zone — but they are the wrong thing to compare a hundred
4417
+ * thousand times: every `foldDaysBetween` in a sort comparator re-parses two
4418
+ * strings and builds two `Date`s. Converting once at the door and working on
4419
+ * integers is 6× faster on the month grid's hot path, with the same output.
4420
+ *
4421
+ * Not on the public surface: it is a representation, not a model.
4422
+ */
4423
+ function foldToEpochDay(date) {
4424
+ return Math.round(toUtcMs(date) / MS_PER_DAY);
4425
+ }
4426
+ /** The inverse of {@link foldToEpochDay}. */
4427
+ function foldFromEpochDay(day) {
4428
+ return fromUtcMs(day * MS_PER_DAY);
4429
+ }
4430
+ /**
4431
+ * Today, read from the **local** clock — the day the user believes it is,
4432
+ * which is what a calendar highlights. Pass `now` to pin it (tests, a
4433
+ * workspace clock).
4434
+ */
4435
+ function foldToday(now = new Date()) {
4436
+ return foldFromNativeDate(now);
4437
+ }
4438
+ /**
4439
+ * A native `Date` read as the plain date it shows on a **local** clock — the
4440
+ * inbound bridge, and the one conversion every consumer would otherwise write
4441
+ * by hand as `date.toISOString().slice(0, 10)`, which is UTC and therefore off
4442
+ * by a day for anyone west of Greenwich after 6pm. That is the exact bug this
4443
+ * whole module exists to remove, so the correct form ships with it.
4444
+ *
4445
+ * Pair with {@link foldToNativeDate} for the way out.
4446
+ */
4447
+ function foldFromNativeDate(date) {
4448
+ return `${pad4(date.getFullYear())}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;
4449
+ }
4450
+ /** `date` shifted by `days` (negative moves back). */
4451
+ function foldAddDays(date, days) {
4452
+ return fromUtcMs(toUtcMs(date) + days * MS_PER_DAY);
4453
+ }
4454
+ /**
4455
+ * Whole days from `from` to `to` — signed, and **exclusive** (same day → `0`),
4456
+ * so a span's length is `foldDaysBetween(start, end) + 1`.
4457
+ */
4458
+ function foldDaysBetween(from, to) {
4459
+ return Math.round((toUtcMs(to) - toUtcMs(from)) / MS_PER_DAY);
4460
+ }
4461
+ /**
4462
+ * `date` shifted by `months`, clamping the day to the target month's length so
4463
+ * the result is always a real date (31 Jan + 1 month → 28/29 Feb, never 3 Mar).
4464
+ */
4465
+ function foldAddMonths(date, months) {
4466
+ const day = Number(date.slice(8, 10));
4467
+ const target = new Date(utcMonthMs(date, months));
4468
+ const lastDay = new Date(lastDayOfMonthMs(target.getUTCFullYear(), target.getUTCMonth())).getUTCDate();
4469
+ return `${pad4(target.getUTCFullYear())}-${pad2(target.getUTCMonth() + 1)}-${pad2(Math.min(day, lastDay))}`;
4470
+ }
4471
+ /** The 1st of `date`'s month shifted by `months`, in epoch ms. */
4472
+ function utcMonthMs(date, months) {
4473
+ const year = Number(date.slice(0, 4));
4474
+ const month = Number(date.slice(5, 7));
4475
+ const at = new Date(Date.UTC(year, month - 1 + months, 1));
4476
+ if (year < 100) {
4477
+ at.setUTCFullYear(year, month - 1 + months, 1);
4478
+ }
4479
+ return at.getTime();
4480
+ }
4481
+ /** Day 0 of the following month — i.e. the last day of this one, in epoch ms. */
4482
+ function lastDayOfMonthMs(year, monthIndex) {
4483
+ const at = new Date(Date.UTC(year, monthIndex + 1, 0));
4484
+ if (year < 100) {
4485
+ at.setUTCFullYear(year, monthIndex + 1, 0);
4486
+ }
4487
+ return at.getTime();
4488
+ }
4489
+ /** The first day of `date`'s month. */
4490
+ function foldStartOfMonth(date) {
4491
+ return `${date.slice(0, 7)}-01`;
4492
+ }
4493
+ /** The last day of `date`'s month (28–31, leap years included). */
4494
+ function foldEndOfMonth(date) {
4495
+ const year = Number(date.slice(0, 4));
4496
+ const month = Number(date.slice(5, 7));
4497
+ return fromUtcMs(lastDayOfMonthMs(year, month - 1));
4498
+ }
4499
+ /**
4500
+ * The start of the week `date` falls in, for a calendar anchored on
4501
+ * `weekStartsOn`. Anchor-agnostic: a Monday-first and a Sunday-first calendar
4502
+ * ask the same question and get their own answer.
4503
+ */
4504
+ function foldStartOfWeek(date, weekStartsOn = "mon") {
4505
+ const ms = toUtcMs(date);
4506
+ const dayOfWeek = new Date(ms).getUTCDay();
4507
+ const offset = (dayOfWeek - WEEKDAY_INDEX[weekStartsOn] + 7) % 7;
4508
+ return fromUtcMs(ms - offset * MS_PER_DAY);
4509
+ }
4510
+ /**
4511
+ * `date`'s position in its week, `0` on the anchor day — the column a day
4512
+ * occupies in a 7-wide grid.
4513
+ */
4514
+ function foldWeekdayIndex(date, weekStartsOn = "mon") {
4515
+ return ((new Date(toUtcMs(date)).getUTCDay() - WEEKDAY_INDEX[weekStartsOn] + 7) % 7);
4516
+ }
4517
+ /**
4518
+ * The ISO-8601 week number `date` falls in, `1`–`53`.
4519
+ *
4520
+ * ISO weeks always start on Monday and belong to the year holding their
4521
+ * **Thursday**, which is why 1 January is sometimes week 52 of the year before
4522
+ * — the rule European B2B reporting runs on, and the reason this cannot be
4523
+ * derived from the calendar's own `weekStartsOn`.
4524
+ */
4525
+ function foldIsoWeek(date) {
4526
+ const day = foldToEpochDay(date);
4527
+ // Epoch day 0 is a Thursday, so `+3` puts Monday at 0.
4528
+ const mondayOffset = (((day + 3) % 7) + 7) % 7;
4529
+ const thursday = day - mondayOffset + 3;
4530
+ const year = Number(foldFromEpochDay(thursday).slice(0, 4));
4531
+ const firstThursdayWeek = foldToEpochDay(`${pad4(year)}-01-01`);
4532
+ return Math.floor((thursday - firstThursdayWeek) / 7) + 1;
4533
+ }
4534
+ /** The ISO week-numbering year of `date` — not always its calendar year. */
4535
+ function foldIsoWeekYear(date) {
4536
+ const day = foldToEpochDay(date);
4537
+ const mondayOffset = (((day + 3) % 7) + 7) % 7;
4538
+ return Number(foldFromEpochDay(day - mondayOffset + 3).slice(0, 4));
4539
+ }
4540
+ /** Which day of the week `date` falls on, anchor-independent. */
4541
+ function foldWeekdayOf(date) {
4542
+ return WEEKDAY_AT[new Date(toUtcMs(date)).getUTCDay()] ?? "sun";
4543
+ }
4544
+ /**
4545
+ * Whether `date` is one of the days a calendar shades as the weekend.
4546
+ *
4547
+ * Takes the weekend **as data**, because it is not derivable from the week's
4548
+ * anchor: the two are independent locale facts. Deriving it — "the last two
4549
+ * columns" — shades Friday and Saturday on a Sunday-first calendar and calls
4550
+ * Sunday a working day, which is wrong everywhere it is used.
4551
+ *
4552
+ * @default FOLD_DEFAULT_WEEKEND_DAYS (`['sat', 'sun']`)
4553
+ */
4554
+ function foldIsWeekend(date, weekendDays = FOLD_DEFAULT_WEEKEND_DAYS) {
4555
+ return weekendDays.includes(foldWeekdayOf(date));
4556
+ }
4557
+ /**
4558
+ * A `Temporal` value read as the plain date it starts with.
4559
+ *
4560
+ * `Temporal.PlainDate.toString()` **is** `YYYY-MM-DD` — the primitive this
4561
+ * module chose for correctness turns out to be exactly Temporal-shaped, which
4562
+ * no `Date`-based calendar can say. Going the other way needs no helper at all:
4563
+ * `Temporal.PlainDate.from(foldDate)` already accepts one of ours.
4564
+ *
4565
+ * A `PlainDateTime` or a `ZonedDateTime` drops its time here, which is the
4566
+ * point — this family plots whole days, and the day you mean is the one in the
4567
+ * value's own calendar and zone, not the one UTC would have given you.
4568
+ *
4569
+ * Returns `""` for anything that is not an ISO date, so a bad value fails
4570
+ * {@link foldIsCalendarDate} rather than silently rendering the wrong month.
4571
+ */
4572
+ function foldFromTemporal(value) {
4573
+ const candidate = value.toString().slice(0, 10);
4574
+ return foldIsCalendarDate(candidate) ? candidate : "";
4575
+ }
4576
+ /**
4577
+ * A `Date` at **UTC midnight** of `date` — the bridge to `Intl.DateTimeFormat`
4578
+ * for month and weekday names. Format it with `timeZone: "UTC"`, or the local
4579
+ * zone will shift the label back a day west of Greenwich.
4580
+ */
4581
+ function foldToNativeDate(date) {
4582
+ return new Date(toUtcMs(date));
4583
+ }
4584
+
4585
+ /**
4586
+ * Wall-clock time for the time grid — `HH:mm`, 24-hour, **no zone**.
4587
+ *
4588
+ * ## Why not an instant
4589
+ * The same decision as the date, for the same reason. A meeting at 09:00 is at
4590
+ * 09:00 on the wall the person is looking at; storing it as an instant means
4591
+ * every render re-derives that wall time from a zone, and the moment the zone
4592
+ * is wrong — a server default, a user travelling, a DST edge — the grid draws
4593
+ * the wrong hour. `"09:00"` cannot be wrong about which hour it means.
4594
+ *
4595
+ * The zone is the **app's** boundary, exactly like the date: convert once on
4596
+ * the way in ({@link foldFromTemporal} takes a `ZonedDateTime` and drops it to
4597
+ * the day and, here, the caller reads its `.hour`/`.minute`), and the calendar
4598
+ * never has to know.
4599
+ *
4600
+ * Times compare **lexicographically** like the dates do — `"09:00" < "14:30"` —
4601
+ * which is what makes overlap detection a string comparison rather than a
4602
+ * parse. `24:00` is legal and means the end of the day, so a span that runs to
4603
+ * midnight has an end greater than its start.
4604
+ */
4605
+ /** Minutes in a day — the grid's own unit. */
4606
+ const FOLD_MINUTES_PER_DAY = 1440;
4607
+ /** `HH:mm`, structurally. Rejects `9:00`, `09:00:00`, junk suffixes. */
4608
+ const TIME_PATTERN = /^\d{2}:\d{2}$/;
4609
+ /**
4610
+ * Whether `value` is a well-formed **and real** wall-clock time.
4611
+ *
4612
+ * The shape check is not enough on its own: `"10:75"` and `"23:60"` both match
4613
+ * `HH:mm` and both sit under 1440 minutes, so a range check on the total would
4614
+ * wave them through. The fields are validated separately — minutes `00`–`59`,
4615
+ * hours `00`–`23`, plus the single `24:00` that means the end of the day — the
4616
+ * same rigour {@link foldIsCalendarDate} applies to `2026-02-30`.
4617
+ */
4618
+ function foldIsCalendarTime(value) {
4619
+ if (!TIME_PATTERN.test(value)) {
4620
+ return false;
4621
+ }
4622
+ const hours = Number(value.slice(0, 2));
4623
+ const minutes = Number(value.slice(3, 5));
4624
+ if (minutes > 59 || hours > 24) {
4625
+ return false;
4626
+ }
4627
+ // Hour 24 exists only as `24:00`, the exclusive end of the day.
4628
+ return hours < 24 || minutes === 0;
4629
+ }
4630
+ /** Minutes since midnight — the number the grid positions on. */
4631
+ function foldToMinutes(time) {
4632
+ return Number(time.slice(0, 2)) * 60 + Number(time.slice(3, 5));
4633
+ }
4634
+ /** Minutes since midnight back to `HH:mm`, clamped to the day. */
4635
+ function foldFromMinutes(minutes) {
4636
+ const clamped = Math.max(0, Math.min(FOLD_MINUTES_PER_DAY, Math.round(minutes)));
4637
+ const hours = Math.floor(clamped / 60);
4638
+ const rest = clamped % 60;
4639
+ return `${hours < 10 ? "0" : ""}${hours}:${rest < 10 ? "0" : ""}${rest}`;
4640
+ }
4641
+ /** `time` shifted by `minutes`, clamped to the day rather than wrapping. */
4642
+ function foldAddMinutes(time, minutes) {
4643
+ return foldFromMinutes(foldToMinutes(time) + minutes);
4644
+ }
4645
+
4646
+ /**
4647
+ * Every date format the calendar family renders, in one table.
4648
+ *
4649
+ * They used to be five components' worth of inline `Intl.DateTimeFormat`
4650
+ * option bags — which is how the day view ended up asking for
4651
+ * `{ weekday: 'long', month: 'long' }` and printing "Saturday May", a phrase in
4652
+ * no locale. Side by side they can be read against each other, and a consumer
4653
+ * can see what a locale is actually asked for.
4654
+ *
4655
+ * `timeZone: 'UTC'` is not optional: a plain date is bridged to `Date` at UTC
4656
+ * midnight, so formatting it in the local zone would print the day before for
4657
+ * anyone west of Greenwich.
4658
+ *
4659
+ * `calendar: 'gregory'` is pinned for the same reason `timeZone` is: the grid
4660
+ * counts days and groups months in the Gregorian calendar (that is what a
4661
+ * `YYYY-MM-DD` string *is*), so the names beside those numbers must be Gregorian
4662
+ * too. Left to the locale's own default, `ar-SA` would print Hijri month names
4663
+ * over Gregorian day numbers on rows that break on Gregorian months — a
4664
+ * calendar contradicting itself. The family is Gregorian, localised into the
4665
+ * language of the locale, not into its calendar system; a non-Gregorian layout
4666
+ * is a separate widget this one does not pretend to be.
4667
+ */
4668
+ const FOLD_CALENDAR_FORMATS = {
4669
+ /** Whole date, spelled out — accessible names. */
4670
+ dateFull: { dateStyle: "long" },
4671
+ /** Whole date **with its weekday** — a period title, where it is the heading. */
4672
+ dateFullWeekday: { dateStyle: "full" },
4673
+ /** `18 May 2026` — the ends of a week, which `formatRange` then joins. */
4674
+ dayMonthYear: { day: "numeric", month: "long", year: "numeric" },
4675
+ /** `Mon` — grid column headers. */
4676
+ weekdayShort: { weekday: "short" },
4677
+ /** `Monday` — a day heading, where there is room. */
4678
+ weekdayLong: { weekday: "long" },
4679
+ /** `May` — beside a day number, never on its own with a weekday. */
4680
+ monthLong: { month: "long" },
4681
+ /** `May` (abbreviated) — the agenda rail, where width is scarce. */
4682
+ monthShort: { month: "short" },
4683
+ /** `May 2026` — a period title. */
4684
+ monthYear: { month: "long", year: "numeric" },
4685
+ /** `18 May` — a span's ends, which `formatRange` then joins. */
4686
+ dayMonthShort: { day: "numeric", month: "short" },
4687
+ /** `09:00` in France, `9:00 AM` in the US — an hour down the grid's gutter. */
4688
+ hourMark: { hour: "numeric", minute: "2-digit" },
4689
+ };
4690
+ /**
4691
+ * Formatters are expensive to build and perfectly reusable, so they are cached
4692
+ * per locale + format. The cache is module-level on purpose: two calendars in
4693
+ * the same locale should not each pay for their own.
4694
+ */
4695
+ const CACHE$1 = new Map();
4696
+ /** The `Intl` formatter for one locale and format, built at most once. */
4697
+ function foldCalendarFormatter(locale, format, override) {
4698
+ const options = { ...FOLD_CALENDAR_FORMATS[format], ...override };
4699
+ const cacheKey = override === undefined
4700
+ ? `${locale ?? ""}|${format}`
4701
+ : `${locale ?? ""}|${format}|${JSON.stringify(options)}`;
4702
+ const cached = CACHE$1.get(cacheKey);
4703
+ if (cached !== undefined) {
4704
+ return cached;
4705
+ }
4706
+ const built = new Intl.DateTimeFormat(locale, {
4707
+ ...options,
4708
+ timeZone: "UTC",
4709
+ calendar: "gregory",
4710
+ });
4711
+ CACHE$1.set(cacheKey, built);
4712
+ return built;
4713
+ }
4714
+ /** One plain date, formatted. */
4715
+ function foldFormatDate(date, locale, format, override) {
4716
+ return foldCalendarFormatter(locale, format, override).format(foldToNativeDate(date));
4717
+ }
4718
+ /**
4719
+ * A span, collapsed to one date when it covers a single day — `Intl`'s own
4720
+ * range formatter does that, and picks the locale's separator.
4721
+ */
4722
+ function foldFormatRange(from, to, locale, format, override) {
4723
+ return foldCalendarFormatter(locale, format, override).formatRange(foldToNativeDate(from), foldToNativeDate(to));
4724
+ }
4725
+ /** A day with no meaning of its own, used only to hang a wall-clock time on. */
4726
+ const TIME_ANCHOR = Date.UTC(2026, 0, 1);
4727
+ /**
4728
+ * A wall-clock time in the locale's own convention — `14:00` in France, `2 PM`
4729
+ * in the United States. Formatted at UTC on a throwaway date, because the time
4730
+ * has no zone and no day: only its hours and minutes are real.
4731
+ */
4732
+ function foldFormatTime(time, locale, override) {
4733
+ const at = new Date(TIME_ANCHOR + foldToMinutes(time) * 60_000);
4734
+ return foldCalendarFormatter(locale, "hourMark", override).format(at);
4735
+ }
4736
+
4737
+ /** The English defaults — the base every override merges onto. */
4738
+ const FOLD_CALENDAR_DEFAULT_LABELS = {
4739
+ grid: "Calendar",
4740
+ today: "today",
4741
+ moreEvents: (count) => `+${count} more`,
4742
+ eventCount: (count) => (count === 1 ? "1 event" : `${count} events`),
4743
+ hiddenCount: (count) => `${count} not shown`,
4744
+ allDay: "All day",
4745
+ hours: "Hours",
4746
+ weekNumberShort: "Wk",
4747
+ weekNumber: (week) => `Week ${week}`,
4748
+ emptyDay: "Nothing on this day.",
4749
+ emptyRange: "Nothing in this range.",
4750
+ todayAction: "Today",
4751
+ previousPeriod: "Previous",
4752
+ nextPeriod: "Next",
4753
+ viewSwitch: "Calendar view",
4754
+ viewMonth: "Month",
4755
+ viewWeek: "Week",
4756
+ viewDay: "Day",
4757
+ viewList: "List",
4758
+ agenda: "Agenda",
4759
+ agendaModes: "Agenda slice",
4760
+ agendaTodo: "To handle",
4761
+ agendaUpcoming: "Upcoming",
4762
+ agendaEmptyTodo: "Nothing to handle — all up to date.",
4763
+ agendaEmptyUpcoming: "Nothing coming up.",
4764
+ agendaMore: (days) => (days === 1 ? "1 more day" : `${days} more days`),
4765
+ relativeToday: "Today",
4766
+ relativeTomorrow: "Tomorrow",
4767
+ collapse: "Collapse",
4768
+ expand: "Expand",
4769
+ sourceFilter: "Filter by source",
4770
+ sourceState: (label, shown) => `${label}, ${shown ? "shown" : "hidden"}`,
4771
+ };
4772
+ /** App-wide calendar labels; defaults to English. */
4773
+ const FOLD_CALENDAR_LABELS = new InjectionToken("FOLD_CALENDAR_LABELS", { factory: () => FOLD_CALENDAR_DEFAULT_LABELS });
4774
+ /** Provide calendar labels for the whole app, merged over the English defaults. */
4775
+ function provideFoldCalendarLabels(labels) {
4776
+ return {
4777
+ provide: FOLD_CALENDAR_LABELS,
4778
+ useValue: { ...FOLD_CALENDAR_DEFAULT_LABELS, ...labels },
4779
+ };
4780
+ }
4781
+
4782
+ /** ISO-8601 day numbers, `1` = Monday … `7` = Sunday — what `Intl` returns. */
4783
+ const ISO_DAY = [
4784
+ "mon",
4785
+ "tue",
4786
+ "wed",
4787
+ "thu",
4788
+ "fri",
4789
+ "sat",
4790
+ "sun",
4791
+ ];
4792
+ /** What a runtime that cannot answer gets: the most common week in the world. */
4793
+ const FOLD_FALLBACK_WEEK_INFO = {
4794
+ weekStartsOn: "mon",
4795
+ weekendDays: ["sat", "sun"],
4796
+ };
4797
+ const CACHE = new Map();
4798
+ /** `1`–`7` → a weekday, ignoring anything out of range. */
4799
+ function toWeekday(isoDay) {
4800
+ if (typeof isoDay !== "number") {
4801
+ return null;
4802
+ }
4803
+ return ISO_DAY[isoDay - 1] ?? null;
4804
+ }
4805
+ /** A no-argument method, narrowed without an assertion. */
4806
+ function isThunk(value) {
4807
+ return typeof value === "function";
4808
+ }
4809
+ /** The `{ firstDay, weekend }` shape, whichever way the runtime exposes it. */
4810
+ function readWeekInfo(locale) {
4811
+ if ("getWeekInfo" in locale) {
4812
+ const method = locale.getWeekInfo;
4813
+ if (isThunk(method)) {
4814
+ return method.call(locale);
4815
+ }
4816
+ }
4817
+ if ("weekInfo" in locale) {
4818
+ return locale.weekInfo;
4819
+ }
4820
+ return null;
4821
+ }
4822
+ /** Narrows the probed value without trusting its shape. */
4823
+ function parseWeekInfo(raw) {
4824
+ if (typeof raw !== "object" || raw === null) {
4825
+ return null;
4826
+ }
4827
+ const firstDay = "firstDay" in raw ? toWeekday(raw.firstDay) : null;
4828
+ const weekendRaw = "weekend" in raw ? raw.weekend : null;
4829
+ const weekend = Array.isArray(weekendRaw)
4830
+ ? weekendRaw
4831
+ .map(toWeekday)
4832
+ .filter((day) => day !== null)
4833
+ : [];
4834
+ if (firstDay === null) {
4835
+ return null;
4836
+ }
4837
+ return {
4838
+ weekStartsOn: firstDay,
4839
+ weekendDays: weekend.length > 0 ? weekend : FOLD_FALLBACK_WEEK_INFO.weekendDays,
4840
+ };
4841
+ }
4842
+ /**
4843
+ * The week conventions of `locale` — its first day and its weekend.
4844
+ *
4845
+ * `undefined` asks the runtime for its own locale, which is what a calendar
4846
+ * with no `locale` input is already formatting in.
4847
+ */
4848
+ function foldLocaleWeekInfo(locale) {
4849
+ const tag = locale ?? new Intl.DateTimeFormat().resolvedOptions().locale;
4850
+ const cached = CACHE.get(tag);
4851
+ if (cached !== undefined) {
4852
+ return cached;
4853
+ }
4854
+ let info = FOLD_FALLBACK_WEEK_INFO;
4855
+ try {
4856
+ info = parseWeekInfo(readWeekInfo(new Intl.Locale(tag))) ?? info;
4857
+ }
4858
+ catch {
4859
+ // A malformed tag is the caller's bug, not a reason to render nothing.
4860
+ info = FOLD_FALLBACK_WEEK_INFO;
4861
+ }
4862
+ CACHE.set(tag, info);
4863
+ return info;
4864
+ }
4865
+
4866
+ /**
4867
+ * The three things every calendar view needs and none of them is about: its
4868
+ * labels, its locale, and the chip template a consumer may have projected.
4869
+ *
4870
+ * Applied through `hostDirectives`, so `locale` and `labels` are still ordinary
4871
+ * inputs on `<fold-calendar-month>`, `<fold-calendar-week>` and the rest — the
4872
+ * consumer sees no difference, and the five views stop carrying five identical
4873
+ * copies of the same eight lines.
4874
+ *
4875
+ * Internal — a view exposes it, it is never applied by hand.
4876
+ *
4877
+ * @example
4878
+ * ```ts
4879
+ * @Component({
4880
+ * hostDirectives: [
4881
+ * { directive: FoldCalendarChromeDirective, inputs: ['locale', 'labels'] },
4882
+ * ],
4883
+ * })
4884
+ * export class FoldCalendarWeekComponent<T = unknown> {
4885
+ * protected readonly chrome = inject(FoldCalendarChromeDirective);
4886
+ * }
4887
+ * ```
4888
+ */
4889
+ class FoldCalendarChromeDirective {
4890
+ /** BCP-47 tag driving every `Intl` name; omitted, the runtime default. */
4891
+ locale = input(/* @ts-ignore */
4892
+ ...(ngDevMode ? [undefined, { debugName: "locale" }] : /* istanbul ignore next */ []));
4893
+ /** Per-instance label overrides, merged over the app-wide (or English) set. */
4894
+ labels = input(/* @ts-ignore */
4895
+ ...(ngDevMode ? [undefined, { debugName: "labels" }] : /* istanbul ignore next */ []));
4896
+ /**
4897
+ * Per-instance `Intl` option overrides, merged over
4898
+ * {@link FOLD_CALENDAR_FORMATS}. Labels let a locale translate; this lets it
4899
+ * **reformat** — a numeric weekday header, a four-digit year, a narrow month.
4900
+ */
4901
+ formats = input(/* @ts-ignore */
4902
+ ...(ngDevMode ? [undefined, { debugName: "formats" }] : /* istanbul ignore next */ []));
4903
+ injectedLabels = inject(FOLD_CALENDAR_LABELS);
4904
+ projectedEvent = contentChild(FoldCalendarEventDirective, /* @ts-ignore */
4905
+ ...(ngDevMode ? [{ debugName: "projectedEvent" }] : /* istanbul ignore next */ []));
4906
+ /** Effective labels — the app-wide (or English) set, with `labels` on top. */
4907
+ l = computed(() => ({
4908
+ ...this.injectedLabels,
4909
+ ...this.labels(),
4910
+ }), /* @ts-ignore */
4911
+ ...(ngDevMode ? [{ debugName: "l" }] : /* istanbul ignore next */ []));
4912
+ /** The custom chip template, when one is projected. */
4913
+ eventContent = computed(() => this.projectedEvent()?.template ?? null, /* @ts-ignore */
4914
+ ...(ngDevMode ? [{ debugName: "eventContent" }] : /* istanbul ignore next */ []));
4915
+ /**
4916
+ * What this locale says its week looks like — the default behind
4917
+ * `weekStartsOn` and `weekendDays`, so a calendar is right in Cairo and
4918
+ * Chicago without the caller having to know either.
4919
+ */
4920
+ weekInfo = computed(() => foldLocaleWeekInfo(this.locale()), /* @ts-ignore */
4921
+ ...(ngDevMode ? [{ debugName: "weekInfo" }] : /* istanbul ignore next */ []));
4922
+ /** The anchor a view should use: the caller's, else the locale's. */
4923
+ anchor(explicit) {
4924
+ return explicit ?? this.weekInfo().weekStartsOn;
4925
+ }
4926
+ /** The weekend a view should shade: the caller's, else the locale's. */
4927
+ weekend(explicit) {
4928
+ return explicit ?? this.weekInfo().weekendDays;
4929
+ }
4930
+ /** One date, in this view's locale and its format overrides. */
4931
+ format(date, format) {
4932
+ return foldFormatDate(date, this.locale(), format, this.formats()?.[format]);
4933
+ }
4934
+ /** A wall-clock time, in this view's locale (24-hour, or AM/PM). */
4935
+ formatTime(time) {
4936
+ return foldFormatTime(time, this.locale(), this.formats()?.hourMark);
4937
+ }
4938
+ /** A span, in this view's locale and its format overrides. */
4939
+ formatRange(from, to, format) {
4940
+ return foldFormatRange(from, to, this.locale(), format, this.formats()?.[format]);
4941
+ }
4942
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCalendarChromeDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4943
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "22.0.8", type: FoldCalendarChromeDirective, isStandalone: true, inputs: { locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null }, formats: { classPropertyName: "formats", publicName: "formats", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "projectedEvent", first: true, predicate: FoldCalendarEventDirective, descendants: true, isSignal: true }], ngImport: i0 });
4944
+ }
4945
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCalendarChromeDirective, decorators: [{
4946
+ type: Directive,
4947
+ args: [{ standalone: true }]
4948
+ }], propDecorators: { locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], formats: [{ type: i0.Input, args: [{ isSignal: true, alias: "formats", required: false }] }], projectedEvent: [{ type: i0.ContentChild, args: [i0.forwardRef(() => FoldCalendarEventDirective), { isSignal: true }] }] } });
4949
+
4950
+ /**
4951
+ * Where a key press moves the focused day. Split out of the component because
4952
+ * "what does ArrowDown mean on a calendar" is a pure question about dates, and
4953
+ * answering it here means it can be tested without a grid, a DOM or a TestBed.
4954
+ *
4955
+ * {@link foldCalendarNextFocus} is public: it is the other half of the geometry
4956
+ * tier, and a hand-rolled date grid owes its users the same arrow keys as the
4957
+ * built-in one. {@link foldFocusDayCell} stays internal — it reads a
4958
+ * `data-fold-day` attribute this package writes, and exporting it would freeze
4959
+ * that attribute into the public contract.
4960
+ */
4961
+ /**
4962
+ * The day `key` moves focus to from `from`, or `null` when the key is not a
4963
+ * grid-navigation key and the component should leave the event alone.
4964
+ *
4965
+ * The mapping is the ARIA grid pattern applied to a date axis: horizontal keys
4966
+ * step a day, vertical keys step a **week** (the column stays put, which is the
4967
+ * whole point of a 7-wide grid), `Home`/`End` snap to the week's bounds, and
4968
+ * `PageUp`/`PageDown` page by month.
4969
+ */
4970
+ function foldCalendarNextFocus(key, from, weekStartsOn = "mon") {
4971
+ switch (key) {
4972
+ case "ArrowLeft":
4973
+ return foldAddDays(from, -1);
4974
+ case "ArrowRight":
4975
+ return foldAddDays(from, 1);
4976
+ case "ArrowUp":
4977
+ return foldAddDays(from, -7);
4978
+ case "ArrowDown":
4979
+ return foldAddDays(from, 7);
4980
+ case "Home":
4981
+ return foldStartOfWeek(from, weekStartsOn);
4982
+ case "End":
4983
+ return foldAddDays(foldStartOfWeek(from, weekStartsOn), 6);
4984
+ case "PageUp":
4985
+ return foldAddMonths(from, -1);
4986
+ case "PageDown":
4987
+ return foldAddMonths(from, 1);
4988
+ default:
4989
+ return null;
4990
+ }
4991
+ }
4992
+ /**
4993
+ * Focuses the cell for `date` inside `grid`, reporting whether it was there to
4994
+ * focus.
4995
+ *
4996
+ * A `false` is the signal that the target lies outside the rendered grid — the
4997
+ * caller pages the calendar and defers the focus until the new cells exist.
4998
+ * Taking the root as an argument keeps this free of `document` (so it is a
4999
+ * no-op under SSR) and means there is exactly **one** answer to "which element
5000
+ * is the grid" — looking it up from the event target as well gave two.
5001
+ */
5002
+ function foldFocusDayCell(grid, date) {
5003
+ // A date can reach this from an input, and it is interpolated into a
5004
+ // selector: anything that is not a plain date is a `SyntaxError` waiting to
5005
+ // happen, so it is refused rather than escaped.
5006
+ if (grid === null || !foldIsCalendarDate(date)) {
5007
+ return false;
5008
+ }
5009
+ const cell = grid.querySelector(`[data-fold-day="${date}"]`);
5010
+ if (cell instanceof HTMLElement) {
5011
+ cell.focus();
5012
+ return true;
5013
+ }
5014
+ return false;
5015
+ }
5016
+
5017
+ class FoldCalendarRovingFocus {
5018
+ host;
5019
+ /** The day holding focus, once the user has moved it. */
5020
+ focused = signal(null, /* @ts-ignore */
5021
+ ...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
5022
+ /** A day to focus once the grid has re-rendered around a new period. */
5023
+ deferred = signal(null, /* @ts-ignore */
5024
+ ...(ngDevMode ? [{ debugName: "deferred" }] : /* istanbul ignore next */ []));
5025
+ constructor(host) {
5026
+ this.host = host;
5027
+ }
5028
+ /**
5029
+ * The cell carrying the tab stop: the focused day while it is on screen,
5030
+ * else the host's fallback — so paging never strands the grid without one.
5031
+ */
5032
+ activeDate = computed(() => {
5033
+ const focused = this.focused();
5034
+ if (focused !== null && this.host.dates().has(focused)) {
5035
+ return focused;
5036
+ }
5037
+ return this.host.fallback();
5038
+ }, /* @ts-ignore */
5039
+ ...(ngDevMode ? [{ debugName: "activeDate" }] : /* istanbul ignore next */ []));
5040
+ tabIndexFor(date) {
5041
+ return date === this.activeDate() ? 0 : -1;
5042
+ }
5043
+ /** A cell took focus — by keyboard, or by a click. */
5044
+ onFocus(date) {
5045
+ this.focused.set(date);
5046
+ }
5047
+ /**
5048
+ * Applies `key` to the date axis, reporting whether it was a navigation key.
5049
+ *
5050
+ * The move happens in place when the target is on screen; when it is not, the
5051
+ * host pages to it and the focus is deferred until the new cells exist.
5052
+ */
5053
+ move(key, from) {
5054
+ const next = foldCalendarNextFocus(key, from, this.host.weekStartsOn());
5055
+ if (next === null) {
5056
+ return false;
5057
+ }
5058
+ this.focused.set(next);
5059
+ if (!foldFocusDayCell(this.host.root(), next)) {
5060
+ this.host.page(next);
5061
+ this.deferred.set(next);
5062
+ this.focusAfterRender();
5063
+ }
5064
+ return true;
5065
+ }
5066
+ /**
5067
+ * Focuses the deferred day once the grid has rendered around the new period.
5068
+ *
5069
+ * `afterNextRender` rather than an `effect`: the ordering of an effect against
5070
+ * the DOM it wants to touch is not contracted and has already changed between
5071
+ * Angular versions, and this way the SSR no-op is explicit. The request is
5072
+ * **consumed**, or repeating the exact same move would be a no-op on the
5073
+ * signal and drop focus onto `<body>`.
5074
+ */
5075
+ focusAfterRender() {
5076
+ afterNextRender(() => {
5077
+ const target = untracked(this.deferred);
5078
+ if (target === null) {
5079
+ return;
5080
+ }
5081
+ this.deferred.set(null);
5082
+ foldFocusDayCell(this.host.root(), target);
5083
+ }, { injector: this.host.injector });
5084
+ }
5085
+ }
5086
+
5087
+ /** A day cell, with every flag a view needs to style it already resolved. */
5088
+ function foldMakeDay(epochDay, context) {
5089
+ const date = foldFromEpochDay(epochDay);
5090
+ return {
5091
+ date,
5092
+ dayOfMonth: Number(date.slice(8, 10)),
5093
+ inMonth: date.slice(0, 7) === context.month,
5094
+ isToday: date === context.today,
5095
+ isWeekend: foldIsWeekend(date, context.weekendDays),
5096
+ eventCount: context.counts.get(epochDay) ?? 0,
5097
+ hiddenCount: context.hidden.get(epochDay) ?? 0,
5098
+ };
5099
+ }
5100
+ /**
5101
+ * How many events cover each day of `[from, to]`, in a single pass.
5102
+ *
5103
+ * A view that asked each cell "which events are on you?" would re-scan the
5104
+ * whole feed once per cell — 35 filters per change-detection cycle on a month
5105
+ * grid. Walking the events instead costs one pass, and the count a cell needs
5106
+ * is then already there.
5107
+ */
5108
+ function foldCountByDay(events, fromDay, toDay) {
5109
+ const counts = new Map();
5110
+ for (const event of events) {
5111
+ const a = foldToEpochDay(event.start);
5112
+ const b = foldToEpochDay(event.end);
5113
+ // Reversed ranges are normalised by the layout, so they are counted the
5114
+ // same way here — otherwise a band would render on days reporting zero.
5115
+ const start = Math.max(fromDay, Math.min(a, b));
5116
+ const end = Math.min(toDay, Math.max(a, b));
5117
+ for (let day = start; day <= end; day += 1) {
5118
+ counts.set(day, (counts.get(day) ?? 0) + 1);
5119
+ }
5120
+ }
5121
+ return counts;
5122
+ }
5123
+ /** No days dropped — the column views' `hidden` map. */
5124
+ const FOLD_NO_HIDDEN_DAYS = new Map();
5125
+ /** The weekend a caller left unset. */
5126
+ function foldWeekendOr(weekendDays) {
5127
+ return weekendDays ?? FOLD_DEFAULT_WEEKEND_DAYS;
5128
+ }
5129
+
5130
+ /**
5131
+ * Packing spans into lanes across a row of days — the month grid's week row,
5132
+ * and the time grid's all-day strip, which are the same problem: spans that may
5133
+ * cross the row, a lane budget, and an honest account of what did not fit.
5134
+ *
5135
+ * Shared rather than copied, because "a hidden span is charged to every day it
5136
+ * would have covered" is a rule that has to mean the same thing in both.
5137
+ */
5138
+ /** The columns a span occupies once clipped to its week row. */
5139
+ function columnsOf(span, weekStartDay, dayCount) {
5140
+ return {
5141
+ startColumn: Math.max(0, span.startDay - weekStartDay),
5142
+ endColumn: Math.min(dayCount - 1, span.endDay - weekStartDay),
5143
+ };
5144
+ }
5145
+ /** One span placed in a lane, or `null` when the budget is spent. */
5146
+ function placeSpan(span, week, columns, laneEnds, maxLanes) {
5147
+ const { startColumn, endColumn } = columns;
5148
+ // First lane whose occupant ends before this span starts; else a fresh one.
5149
+ const reusable = laneEnds.findIndex((end) => end < startColumn);
5150
+ const lane = reusable === -1 ? laneEnds.length : reusable;
5151
+ if (lane >= maxLanes) {
5152
+ return null;
5153
+ }
5154
+ laneEnds[lane] = endColumn;
5155
+ const weekEndDay = week.startDay + week.dayCount - 1;
5156
+ const startsHere = span.startDay >= week.startDay && !span.openStart;
5157
+ const endsHere = span.endDay <= weekEndDay && !span.openEnd;
5158
+ return {
5159
+ key: `${span.key}@${week.start}`,
5160
+ event: span.event,
5161
+ startColumn,
5162
+ endColumn,
5163
+ lane,
5164
+ continuesBefore: !startsHere,
5165
+ continuesAfter: !endsHere,
5166
+ groupSize: span.groupSize,
5167
+ // A half-day edge only means something on the segment holding the real edge.
5168
+ ...(startsHere && span.event.startHalfDay !== undefined
5169
+ ? { startHalfDay: span.event.startHalfDay }
5170
+ : {}),
5171
+ ...(endsHere && span.event.endHalfDay !== undefined
5172
+ ? { endHalfDay: span.event.endHalfDay }
5173
+ : {}),
5174
+ };
5175
+ }
5176
+ /**
5177
+ * Packs a row's candidates into lanes, and records what overflowed **per day**
5178
+ * — a row-wide total would say a week is crowded without saying which day to
5179
+ * look at, so each date carries its own count.
5180
+ */
5181
+ function foldPackRow(candidates, week, maxLanes, hidden) {
5182
+ const laneEnds = [];
5183
+ const bands = [];
5184
+ let hiddenCount = 0;
5185
+ for (const span of candidates) {
5186
+ const columns = columnsOf(span, week.startDay, week.dayCount);
5187
+ const band = placeSpan(span, week, columns, laneEnds, maxLanes);
5188
+ if (band !== null) {
5189
+ bands.push(band);
5190
+ continue;
5191
+ }
5192
+ hiddenCount += 1;
5193
+ // A hidden span is missing from every day it would have covered.
5194
+ for (let column = columns.startColumn; column <= columns.endColumn; column += 1) {
5195
+ const day = week.startDay + column;
5196
+ hidden.set(day, (hidden.get(day) ?? 0) + 1);
5197
+ }
5198
+ }
5199
+ return { bands, hiddenCount };
5200
+ }
5201
+
5202
+ /**
5203
+ * How loudly a tone asks to be looked at. A collapsed group shows its most
5204
+ * severe member, so an alert inside a group is never hidden behind a member
5205
+ * that happens to come first.
5206
+ */
5207
+ const TONE_SEVERITY = {
5208
+ alert: 4,
5209
+ warning: 3,
5210
+ success: 2,
5211
+ neutral: 1,
5212
+ muted: 0,
5213
+ };
5214
+ /** Severity of an event's tone, `neutral` when it declares none. */
5215
+ function severityOf(event) {
5216
+ return TONE_SEVERITY[event.tone ?? "neutral"];
5217
+ }
5218
+ /**
5219
+ * The span a single event occupies, with a reversed range put back in order.
5220
+ *
5221
+ * `end < start` is a caller bug, and left alone it renders **twice, wrongly**:
5222
+ * the grid's `grid-column: 5 / 2` is silently swapped by CSS so a band covers
5223
+ * days it does not cover, while every other view — which asks
5224
+ * `start <= day && end >= day` — reports the event as absent. Swapping makes
5225
+ * the two agree, and the dev warning names the event rather than leaving the
5226
+ * contradiction to be discovered in a screenshot.
5227
+ */
5228
+ function spanOf(event, key) {
5229
+ const reversed = event.end < event.start;
5230
+ if (reversed && isDevMode()) {
5231
+ console.warn(`[fold-calendar] event "${event.id}" ends (${event.end}) before it starts (${event.start}); treating the range as reversed.`);
5232
+ }
5233
+ const start = reversed ? event.end : event.start;
5234
+ const end = reversed ? event.start : event.end;
5235
+ return {
5236
+ key,
5237
+ event,
5238
+ start,
5239
+ end,
5240
+ startDay: foldToEpochDay(start),
5241
+ endDay: foldToEpochDay(end),
5242
+ groupSize: 1,
5243
+ openStart: event.openStart === true,
5244
+ openEnd: event.openEnd === true,
5245
+ };
5246
+ }
5247
+ /** Warns once per render when two events share an id. */
5248
+ function warnDuplicateIds(events) {
5249
+ const seen = new Set();
5250
+ for (const event of events) {
5251
+ if (seen.has(event.id)) {
5252
+ console.warn(`[fold-calendar] two events share the id "${event.id}". Ids must be unique per render — use \`groupId\` to collapse events on purpose.`);
5253
+ return;
5254
+ }
5255
+ seen.add(event.id);
5256
+ }
5257
+ }
5258
+ /**
5259
+ * Merges a member into the span standing for its group: the **union** of the
5260
+ * ranges (a group chip stopping short of a member it stands for would be a
5261
+ * lie), open on either side if **any** member is, and represented by the most
5262
+ * severe member so tone, icon, label and source all come from one event that
5263
+ * really exists.
5264
+ */
5265
+ function mergeMember(span, member) {
5266
+ const takeover = severityOf(member.event) > severityOf(span.event);
5267
+ const opensFirst = span.startDay <= member.startDay;
5268
+ const endsLast = span.endDay >= member.endDay;
5269
+ return {
5270
+ key: span.key,
5271
+ event: takeover ? member.event : span.event,
5272
+ start: opensFirst ? span.start : member.start,
5273
+ end: endsLast ? span.end : member.end,
5274
+ startDay: opensFirst ? span.startDay : member.startDay,
5275
+ endDay: endsLast ? span.endDay : member.endDay,
5276
+ groupSize: span.groupSize + 1,
5277
+ openStart: span.openStart || member.openStart,
5278
+ openEnd: span.openEnd || member.openEnd,
5279
+ };
5280
+ }
5281
+ /**
5282
+ * One span per event, except where `groupId` asks for a collapsed chip.
5283
+ *
5284
+ * Identity keys carry the event's position, so two events that share an id stay
5285
+ * two spans: merging them would drop one silently, which is not something a
5286
+ * duplicated key should be able to do. Insertion order is preserved, so the
5287
+ * caller's ordering still decides ties.
5288
+ */
5289
+ function foldCollapseGroups(events) {
5290
+ if (isDevMode()) {
5291
+ warnDuplicateIds(events);
5292
+ }
5293
+ const spans = new Map();
5294
+ events.forEach((event, index) => {
5295
+ const key = event.groupId === undefined
5296
+ ? `event:${index}:${event.id}`
5297
+ : `group:${event.groupId}`;
5298
+ const incoming = spanOf(event, key);
5299
+ const existing = spans.get(key);
5300
+ spans.set(key, existing === undefined ? incoming : mergeMember(existing, incoming));
5301
+ });
5302
+ return [...spans.values()];
5303
+ }
5304
+
5305
+ /** Columns in a week row. */
5306
+ const DAYS_PER_WEEK$1 = 7;
5307
+ /** Rows a fixed-height month grid always emits. */
5308
+ const FIXED_WEEK_COUNT = 6;
5309
+ /** Lanes a week row stacks when the caller does not say. */
5310
+ const DEFAULT_MAX_LANES = 3;
5311
+ /**
5312
+ * Every row's candidates, in one pass over the feed.
5313
+ *
5314
+ * Filtering the whole feed once per row is `O(rows × N)` and, worse, compares
5315
+ * strings; walking the feed once and dropping each span into the rows it
5316
+ * touches is `O(N)` on integers. At 5 000 events this part of the layout went
5317
+ * from 12.6 ms to 1.9 ms.
5318
+ *
5319
+ * Each row is then ordered the way a reader scans it: earliest start first, and
5320
+ * on a tie the longest span first, so the bars crossing the most days settle
5321
+ * into the top lanes.
5322
+ */
5323
+ function bucketByRow(spans, firstRowDay, rowCount) {
5324
+ const rows = Array.from({ length: rowCount }, () => []);
5325
+ const lastDay = firstRowDay + rowCount * DAYS_PER_WEEK$1 - 1;
5326
+ for (const span of spans) {
5327
+ if (span.endDay < firstRowDay || span.startDay > lastDay) {
5328
+ continue;
5329
+ }
5330
+ const from = Math.max(0, Math.floor((span.startDay - firstRowDay) / DAYS_PER_WEEK$1));
5331
+ const to = Math.min(rowCount - 1, Math.floor((span.endDay - firstRowDay) / DAYS_PER_WEEK$1));
5332
+ for (let row = from; row <= to; row += 1) {
5333
+ rows[row]?.push(span);
5334
+ }
5335
+ }
5336
+ for (const row of rows) {
5337
+ row.sort((a, b) => a.startDay !== b.startDay
5338
+ ? a.startDay - b.startDay
5339
+ : b.endDay - b.startDay - (a.endDay - a.startDay));
5340
+ }
5341
+ return rows;
5342
+ }
5343
+ /**
5344
+ * Lanes a row may stack, as one number the whole grid agrees on.
5345
+ *
5346
+ * The clamp lives here alone: a component that also clamped would be a second
5347
+ * copy of the invariant, and the copy that forgot `NaN` would disable the lane
5348
+ * budget entirely (`lane >= NaN` is never true) while the grid template turned
5349
+ * `repeat(NaN, …)` into a dropped rule.
5350
+ */
5351
+ function foldClampLanes(value) {
5352
+ if (value === undefined || !Number.isFinite(value)) {
5353
+ return DEFAULT_MAX_LANES;
5354
+ }
5355
+ return Math.max(0, Math.trunc(value));
5356
+ }
5357
+ /** The grid's own window — whole weeks, padded out when `fixedWeeks` asks. */
5358
+ function gridBounds(options) {
5359
+ const weekStartsOn = options.weekStartsOn ?? "mon";
5360
+ const firstRowDay = foldToEpochDay(foldStartOfWeek(foldStartOfMonth(options.month), weekStartsOn));
5361
+ const lastRowDay = foldToEpochDay(foldStartOfWeek(foldEndOfMonth(options.month), weekStartsOn));
5362
+ const naturalRows = (lastRowDay - firstRowDay) / DAYS_PER_WEEK$1 + 1;
5363
+ return {
5364
+ firstRowDay,
5365
+ rowCount: options.fixedWeeks === true
5366
+ ? Math.max(FIXED_WEEK_COUNT, naturalRows)
5367
+ : naturalRows,
5368
+ };
5369
+ }
5370
+ /**
5371
+ * Lays a month out as week rows with their events already packed into lanes.
5372
+ *
5373
+ * The grid runs from the week containing the 1st to the week containing the
5374
+ * last day, so it always starts and ends on whole weeks; `fixedWeeks` pads it
5375
+ * to six rows. Events outside that window are dropped, and an event crossing a
5376
+ * week boundary yields one band per week it touches.
5377
+ *
5378
+ * An unparseable `month` yields **no rows** rather than a grid of `Invalid
5379
+ * Date` cells, with a dev-mode warning naming the value.
5380
+ */
5381
+ function foldBuildMonthGrid(events, options) {
5382
+ if (!foldIsCalendarDate(options.month)) {
5383
+ if (isDevMode()) {
5384
+ console.warn(`[fold-calendar] "${options.month}" is not a YYYY-MM-DD date; the grid is empty.`);
5385
+ }
5386
+ return [];
5387
+ }
5388
+ const maxLanes = foldClampLanes(options.maxLanes);
5389
+ const { firstRowDay, rowCount } = gridBounds(options);
5390
+ const rows = bucketByRow(foldCollapseGroups(events), firstRowDay, rowCount);
5391
+ const hidden = new Map();
5392
+ const packed = rows.map((candidates, row) => {
5393
+ const week = {
5394
+ startDay: firstRowDay + row * DAYS_PER_WEEK$1,
5395
+ start: foldFromEpochDay(firstRowDay + row * DAYS_PER_WEEK$1),
5396
+ dayCount: DAYS_PER_WEEK$1,
5397
+ };
5398
+ return { week, ...foldPackRow(candidates, week, maxLanes, hidden) };
5399
+ });
5400
+ const context = {
5401
+ month: options.month.slice(0, 7),
5402
+ today: options.today,
5403
+ weekendDays: foldWeekendOr(options.weekendDays),
5404
+ counts: foldCountByDay(events, firstRowDay, firstRowDay + rowCount * DAYS_PER_WEEK$1 - 1),
5405
+ hidden,
5406
+ };
5407
+ return packed.map((row) => ({
5408
+ start: row.week.start,
5409
+ days: buildDays(row.week.startDay, context),
5410
+ bands: row.bands,
5411
+ hiddenCount: row.hiddenCount,
5412
+ }));
5413
+ }
5414
+ /** The seven cells of one week row. */
5415
+ function buildDays(weekStartDay, context) {
5416
+ return Array.from({ length: DAYS_PER_WEEK$1 }, (_unused, offset) => foldMakeDay(weekStartDay + offset, context));
5417
+ }
5418
+
5419
+ /**
5420
+ * Replaces the **inside of a month day cell** — by default just the day number.
5421
+ *
5422
+ * This is the hook for everything the calendar cannot know: a holiday marker, a
5423
+ * closure, "3/8 staffed", a price. The cell itself (the button, its focus
5424
+ * behaviour, its accessible name) stays the calendar's job.
5425
+ *
5426
+ * ```html
5427
+ * <ng-template foldCalendarDay let-day>
5428
+ * <span>{{ day.dayOfMonth }}</span>
5429
+ * @if (closed(day.date)) { <fold-icon name="lock" size="xs" /> }
5430
+ * </ng-template>
5431
+ * ```
5432
+ *
5433
+ * @selector `ng-template[foldCalendarDay]`
5434
+ */
5435
+ class FoldCalendarDayDirective {
5436
+ template = inject(TemplateRef);
5437
+ static ngTemplateContextGuard(_directive, context) {
5438
+ // Compile-time only — Angular never calls this; the narrowing is the point.
5439
+ return context !== undefined;
5440
+ }
5441
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCalendarDayDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
5442
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.8", type: FoldCalendarDayDirective, isStandalone: true, selector: "ng-template[foldCalendarDay]", ngImport: i0 });
5443
+ }
5444
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCalendarDayDirective, decorators: [{
5445
+ type: Directive,
5446
+ args: [{ selector: "ng-template[foldCalendarDay]", standalone: true }]
5447
+ }] });
5448
+ /**
5449
+ * Replaces the **day heading** of the agenda rail — by default a big day number
5450
+ * with a relative name beside it. Where an app puts a per-day count, a total,
5451
+ * or a quick action.
5452
+ *
5453
+ * @selector `ng-template[foldCalendarHeading]`
5454
+ */
5455
+ class FoldCalendarHeadingDirective {
5456
+ template = inject(TemplateRef);
5457
+ static ngTemplateContextGuard(_directive, context) {
5458
+ // Compile-time only — Angular never calls this; the narrowing is the point.
5459
+ return context !== undefined;
5460
+ }
5461
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCalendarHeadingDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
5462
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.8", type: FoldCalendarHeadingDirective, isStandalone: true, selector: "ng-template[foldCalendarHeading]", ngImport: i0 });
5463
+ }
5464
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCalendarHeadingDirective, decorators: [{
5465
+ type: Directive,
5466
+ args: [{ selector: "ng-template[foldCalendarHeading]", standalone: true }]
5467
+ }] });
5468
+ /**
5469
+ * Replaces the toolbar's **period title** — by default an `<h2>` holding
5470
+ * `foldViewTitle()`. Project one to change the wording, or to give the title a
5471
+ * heading level that fits the page's outline.
5472
+ *
5473
+ * @selector `ng-template[foldCalendarTitle]`
5474
+ */
5475
+ class FoldCalendarTitleDirective {
5476
+ template = inject(TemplateRef);
5477
+ static ngTemplateContextGuard(_directive, context) {
5478
+ // Compile-time only — Angular never calls this; the narrowing is the point.
5479
+ return context !== undefined;
5480
+ }
5481
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCalendarTitleDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
5482
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.8", type: FoldCalendarTitleDirective, isStandalone: true, selector: "ng-template[foldCalendarTitle]", ngImport: i0 });
5483
+ }
5484
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCalendarTitleDirective, decorators: [{
5485
+ type: Directive,
5486
+ args: [{ selector: "ng-template[foldCalendarTitle]", standalone: true }]
5487
+ }] });
5488
+ /**
5489
+ * Replaces the month view's **`+N` chip** — by default a small text button.
5490
+ * Where an app puts an avatar stack, or the trigger of a "see all" popover.
5491
+ *
5492
+ * @selector `ng-template[foldCalendarOverflow]`
5493
+ */
5494
+ class FoldCalendarOverflowDirective {
5495
+ template = inject(TemplateRef);
5496
+ static ngTemplateContextGuard(_directive, context) {
5497
+ // Compile-time only — Angular never calls this; the narrowing is the point.
5498
+ return context !== undefined;
5499
+ }
5500
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCalendarOverflowDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
5501
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.8", type: FoldCalendarOverflowDirective, isStandalone: true, selector: "ng-template[foldCalendarOverflow]", ngImport: i0 });
5502
+ }
5503
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCalendarOverflowDirective, decorators: [{
5504
+ type: Directive,
5505
+ args: [{ selector: "ng-template[foldCalendarOverflow]", standalone: true }]
5506
+ }] });
5507
+
5508
+ /** A Monday, used only to walk seven weekday names out of `Intl`. */
5509
+ const WEEKDAY_SAMPLE = "2026-01-05";
5510
+ /** Keys that activate a day cell — the ones a `<button>` would have handled. */
5511
+ const ACTIVATION_KEYS = new Set(["Enter", " ", "Spacebar"]);
5512
+ /**
5513
+ * `<fold-calendar-month>` — a month grid where events span the days they cover.
5514
+ *
5515
+ * The grid is a **date axis**: seven columns of whole days, and over them a
5516
+ * layer of bands, each stretching from the column its event starts on to the
5517
+ * column it ends on. An event crossing a week boundary is drawn once per week
5518
+ * with an open edge on the side that continues, so a three-week holiday reads
5519
+ * as one thing rather than twenty-one chips.
5520
+ *
5521
+ * Dates are plain `YYYY-MM-DD` strings — see `calendar-date.ts` for why that,
5522
+ * and not `Date`.
5523
+ *
5524
+ * ## Inputs
5525
+ * | Input | Type | Default | Meaning |
5526
+ * |----------------|-------------------------------|---------------|---------|
5527
+ * | `month` | `FoldCalendarDate` | — | **Two-way.** Any date in the month on display; keyboard paging writes back. |
5528
+ * | `events` | `FoldCalendarEvent<T>[]` | `[]` | What to plot. Anything outside the grid is ignored. |
5529
+ * | `today` | `FoldCalendarDate` | — | The day to mark. Omitted, nothing is marked — pass `foldToday()`. |
5530
+ * | `weekStartsOn` | `FoldWeekday` | the locale's | Which day opens the row — `Intl.Locale` knows, so it is only an override. |
5531
+ * | `weekendDays` | `FoldWeekday[]` | the locale's | Which days are shaded — a separate fact from the anchor, and also in the locale. |
5532
+ * | `showWeekNumbers` | `boolean` | `false` | An ISO week number in a leading column. |
5533
+ * | `maxLanes` | `number` | `3` | Bands a week stacks before the rest collapse into overflow chips. |
5534
+ * | `fixedWeeks` | `boolean` | `false` | Always six rows, so the grid keeps one height across months. |
5535
+ * | `dayModifiers` | `(day) => string[]` | — | Extra names per cell, emitted as `data-fold-day-modifiers`. |
5536
+ * | `locale` | `string` | runtime | Drives month/weekday names through `Intl`. |
5537
+ * | `labels` | `Partial<FoldCalendarLabels>` | — | Per-instance overrides of the chrome strings. |
5538
+ *
5539
+ * ## Outputs
5540
+ * | Output | Payload | Fires on |
5541
+ * |-----------------|------------------------|----------|
5542
+ * | `dayClick` | `FoldCalendarDate` | A day cell activated by click, `Enter` or `Space`. |
5543
+ * | `eventClick` | `FoldCalendarEvent<T>` | A band clicked. Grouped bands emit their representative. |
5544
+ * | `overflowClick` | `FoldCalendarDate` | A `+N` chip — the **day** whose events overflowed, so the caller knows which one to open. |
5545
+ *
5546
+ * ## Templates
5547
+ * | Directive | Replaces |
5548
+ * |------------------------|----------|
5549
+ * | `foldCalendarEvent` | The inside of a band — icon + label + count. |
5550
+ * | `foldCalendarDay` | The inside of a day cell — the day number. |
5551
+ * | `foldCalendarOverflow` | The `+N` chip. |
5552
+ *
5553
+ * ## Theming
5554
+ * | CSS variable | Default | Sets |
5555
+ * |---------------------------------------|---------|------|
5556
+ * | `--fold-calendar-month-header-row` | `30px` | Height of the day-number row. |
5557
+ * | `--fold-calendar-month-lane` | `22px` | Height of one band lane. |
5558
+ * | `--fold-calendar-month-row-min-height`| `112px` | Minimum height of a week row. |
5559
+ * | `--fold-calendar-month-daynum-size` | `22px` | The day number's round box. |
5560
+ * | `--fold-calendar-month-cell-padding` | `--fold-space-sm` | Padding inside a day cell. |
5561
+ * | `--fold-calendar-month-week-number-width` | `34px` | The ISO week column, when shown. |
5562
+ * | `--fold-calendar-bar-width` | `3px` | The tone bar down a band's leading edge. |
5563
+ * | `--fold-calendar-band-radius` | `--fold-radius-sm` | Band and chip corners. |
5564
+ * | `--fold-calendar-band-gutter` | `--fold-space-xs` | Gap between a band and its cell edge. |
5565
+ *
5566
+ * ## Accessibility
5567
+ * The **date grid** is the ARIA structure: `role="grid"` over rows of
5568
+ * `gridcell` days, with one roving tab stop (the focused day, else today, else
5569
+ * the 1st) and the grid pattern on the arrow keys — horizontal steps a day,
5570
+ * vertical steps a week, `Home`/`End` snap to the week, and `PageUp`/`PageDown`
5571
+ * page the month, writing back through `month`.
5572
+ *
5573
+ * Bands cannot be `gridcell`s — they span columns, which is the whole point —
5574
+ * so they are **`aria-hidden` mouse affordances**, and each day cell instead
5575
+ * announces its date, the today marker, its **event count** and, when the lane
5576
+ * budget hid some, **how many it is not showing**.
5577
+ *
5578
+ * That makes `dayClick` **load-bearing, not optional**: it is the accessible
5579
+ * path into an individual event. A month that handles only `eventClick` is a
5580
+ * view whose events no keyboard can reach — wire `dayClick` to a day or list
5581
+ * view, as the gallery does.
5582
+ *
5583
+ * @selector `fold-calendar-month`
5584
+ *
5585
+ * @example
5586
+ * ```html
5587
+ * <fold-calendar-month
5588
+ * [(month)]="month"
5589
+ * [events]="events()"
5590
+ * [today]="today"
5591
+ * (dayClick)="openDay($event)"
5592
+ * (eventClick)="openEvent($event)"
5593
+ * />
5594
+ * ```
5595
+ */
5596
+ class FoldCalendarMonthComponent {
5597
+ /** Any date inside the month on display. Two-way: keyboard paging writes back. */
5598
+ month = model.required(/* @ts-ignore */
5599
+ ...(ngDevMode ? [{ debugName: "month" }] : /* istanbul ignore next */ []));
5600
+ /** Events to plot; those missing the grid entirely are ignored. */
5601
+ events = input([], /* @ts-ignore */
5602
+ ...(ngDevMode ? [{ debugName: "events" }] : /* istanbul ignore next */ []));
5603
+ /** The day to mark as today. Omit and none is marked (an SSR-stable default). */
5604
+ today = input(/* @ts-ignore */
5605
+ ...(ngDevMode ? [undefined, { debugName: "today" }] : /* istanbul ignore next */ []));
5606
+ /** Day the weeks start on. @default the locale's own first day */
5607
+ weekStartsOn = input(/* @ts-ignore */
5608
+ ...(ngDevMode ? [undefined, { debugName: "weekStartsOn" }] : /* istanbul ignore next */ []));
5609
+ /** Days shaded as the weekend. @default the locale's own weekend */
5610
+ weekendDays = input(/* @ts-ignore */
5611
+ ...(ngDevMode ? [undefined, { debugName: "weekendDays" }] : /* istanbul ignore next */ []));
5612
+ /** Show the ISO week number in a leading column. */
5613
+ showWeekNumbers = input(false, { ...(ngDevMode ? { debugName: "showWeekNumbers" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
5614
+ /** Bands a week stacks before the rest collapse into overflow chips. @default 3 */
5615
+ maxLanes = input(3, { ...(ngDevMode ? { debugName: "maxLanes" } : /* istanbul ignore next */ {}), transform: numberAttribute });
5616
+ /** Always emit six rows, so the grid keeps one height across months. */
5617
+ fixedWeeks = input(false, { ...(ngDevMode ? { debugName: "fixedWeeks" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
5618
+ /** Extra names for a cell, emitted as `data-fold-day-modifiers`. */
5619
+ dayModifiers = input(/* @ts-ignore */
5620
+ ...(ngDevMode ? [undefined, { debugName: "dayModifiers" }] : /* istanbul ignore next */ []));
5621
+ /** A day cell was activated. */
5622
+ dayClick = output();
5623
+ /** A band was clicked; a grouped band emits its representative event. */
5624
+ eventClick = output();
5625
+ /** A `+N` chip was clicked, with the day whose events overflowed. */
5626
+ overflowClick = output();
5627
+ /** Labels, locale and the projected chip template — see the directive. */
5628
+ chrome = inject(FoldCalendarChromeDirective);
5629
+ host = inject(ElementRef);
5630
+ projectedDay = contentChild(FoldCalendarDayDirective, /* @ts-ignore */
5631
+ ...(ngDevMode ? [{ debugName: "projectedDay" }] : /* istanbul ignore next */ []));
5632
+ projectedOverflow = contentChild(FoldCalendarOverflowDirective, /* @ts-ignore */
5633
+ ...(ngDevMode ? [{ debugName: "projectedOverflow" }] : /* istanbul ignore next */ []));
5634
+ /** The custom day-cell template, when one is projected. */
5635
+ dayContent = computed(() => this.projectedDay()?.template ?? null, /* @ts-ignore */
5636
+ ...(ngDevMode ? [{ debugName: "dayContent" }] : /* istanbul ignore next */ []));
5637
+ /** The custom overflow-chip template, when one is projected. */
5638
+ overflowContent = computed(() => this.projectedOverflow()?.template ?? null, /* @ts-ignore */
5639
+ ...(ngDevMode ? [{ debugName: "overflowContent" }] : /* istanbul ignore next */ []));
5640
+ /**
5641
+ * Lanes this grid stacks — clamped once, by the layout, and read back here so
5642
+ * the geometry and the CSS row template can never disagree.
5643
+ */
5644
+ lanes = computed(() => foldClampLanes(this.maxLanes()), /* @ts-ignore */
5645
+ ...(ngDevMode ? [{ debugName: "lanes" }] : /* istanbul ignore next */ []));
5646
+ /** The laid-out grid: week rows, their days, and their packed bands. */
5647
+ /** The anchor in force: the caller's, else the one the locale declares. */
5648
+ anchor = computed(() => this.chrome.anchor(this.weekStartsOn()), /* @ts-ignore */
5649
+ ...(ngDevMode ? [{ debugName: "anchor" }] : /* istanbul ignore next */ []));
5650
+ weeks = computed(() => foldBuildMonthGrid(this.events(), {
5651
+ month: this.month(),
5652
+ weekStartsOn: this.anchor(),
5653
+ weekendDays: this.chrome.weekend(this.weekendDays()),
5654
+ maxLanes: this.lanes(),
5655
+ fixedWeeks: this.fixedWeeks(),
5656
+ today: this.today(),
5657
+ }), /* @ts-ignore */
5658
+ ...(ngDevMode ? [{ debugName: "weeks" }] : /* istanbul ignore next */ []));
5659
+ /** Column headers, walked out of `Intl` so every locale comes for free. */
5660
+ weekdayNames = computed(() => {
5661
+ const sample = foldStartOfWeek(WEEKDAY_SAMPLE, this.anchor());
5662
+ return Array.from({ length: 7 }, (_unused, offset) => this.chrome.format(foldAddDays(sample, offset), "weekdayShort"));
5663
+ }, /* @ts-ignore */
5664
+ ...(ngDevMode ? [{ debugName: "weekdayNames" }] : /* istanbul ignore next */ []));
5665
+ /**
5666
+ * Columns before the first day. The week-number column is a real grid column,
5667
+ * so everything placed by index — cells, bands, overflow chips — shifts with
5668
+ * it, and nothing has to know why.
5669
+ */
5670
+ columnOffset = computed(() => this.showWeekNumbers() ? 1 : 0, /* @ts-ignore */
5671
+ ...(ngDevMode ? [{ debugName: "columnOffset" }] : /* istanbul ignore next */ []));
5672
+ /** The ISO week each row belongs to — always Monday-based, whatever the anchor. */
5673
+ weekNumber(week) {
5674
+ return foldIsoWeek(week.start);
5675
+ }
5676
+ /**
5677
+ * Row track sizes: the day-number row, one row per lane, then the overflow
5678
+ * row. Both heights are themeable knobs, which is why they are `var()`s.
5679
+ */
5680
+ rowTemplate = computed(() => `var(--fold-calendar-month-header-row, 30px) repeat(${this.lanes()}, var(--fold-calendar-month-lane, 22px)) auto`, /* @ts-ignore */
5681
+ ...(ngDevMode ? [{ debugName: "rowTemplate" }] : /* istanbul ignore next */ []));
5682
+ /** The overflow chips' row — always the one after the last lane. */
5683
+ overflowRow = computed(() => this.lanes() + 2, /* @ts-ignore */
5684
+ ...(ngDevMode ? [{ debugName: "overflowRow" }] : /* istanbul ignore next */ []));
5685
+ /** Every date the grid currently shows — the bound focus may not leave. */
5686
+ gridDates = computed(() => new Set(this.weeks().flatMap((week) => week.days.map((day) => day.date))), /* @ts-ignore */
5687
+ ...(ngDevMode ? [{ debugName: "gridDates" }] : /* istanbul ignore next */ []));
5688
+ /** Where the tab stop sits before the user moves it: today, else the 1st. */
5689
+ defaultStop = computed(() => {
5690
+ const today = this.today();
5691
+ if (today !== undefined && this.gridDates().has(today)) {
5692
+ return today;
5693
+ }
5694
+ return (this.weeks()
5695
+ .flatMap((week) => week.days)
5696
+ .find((day) => day.inMonth)?.date ?? null);
5697
+ }, /* @ts-ignore */
5698
+ ...(ngDevMode ? [{ debugName: "defaultStop" }] : /* istanbul ignore next */ []));
5699
+ /** The grid's single tab stop and the arrow keys that move it. */
5700
+ focus = new FoldCalendarRovingFocus({
5701
+ dates: this.gridDates,
5702
+ fallback: this.defaultStop,
5703
+ weekStartsOn: this.anchor,
5704
+ root: () => this.rootElement(),
5705
+ page: (date) => this.month.set(date),
5706
+ injector: inject(Injector),
5707
+ });
5708
+ dayTabIndex(date) {
5709
+ return this.focus.tabIndexFor(date);
5710
+ }
5711
+ /** A cell's caller-supplied names, as one `~=`-matchable attribute. */
5712
+ dayModifierAttr(day) {
5713
+ const names = this.dayModifiers()?.(day) ?? [];
5714
+ return names.length === 0 ? null : names.join(" ");
5715
+ }
5716
+ /**
5717
+ * A day's accessible name: its date, the today marker, what sits on it, and
5718
+ * what the lane budget is keeping from it.
5719
+ */
5720
+ dayLabel(day) {
5721
+ const labels = this.chrome.l();
5722
+ const parts = [this.chrome.format(day.date, "dateFull")];
5723
+ if (day.isToday) {
5724
+ parts.push(labels.today);
5725
+ }
5726
+ if (day.eventCount > 0) {
5727
+ parts.push(labels.eventCount(day.eventCount));
5728
+ }
5729
+ if (day.hiddenCount > 0) {
5730
+ parts.push(labels.hiddenCount(day.hiddenCount));
5731
+ }
5732
+ return parts.join(", ");
5733
+ }
5734
+ /** A band's text: the group's label when it stands for several events. */
5735
+ bandLabel(band) {
5736
+ if (band.groupSize > 1) {
5737
+ return band.event.groupLabel ?? band.event.label;
5738
+ }
5739
+ return band.event.label;
5740
+ }
5741
+ /** The leading glyph — suppressed on a continuation, which has no real start. */
5742
+ bandIcon(band) {
5743
+ if (band.continuesBefore) {
5744
+ return null;
5745
+ }
5746
+ return band.event.icon ?? null;
5747
+ }
5748
+ onDayFocus(date) {
5749
+ this.focus.onFocus(date);
5750
+ }
5751
+ /** A cell is not a `<button>`, so activation is wired by hand. */
5752
+ onDayKeydown(event, date) {
5753
+ if (event.target !== event.currentTarget) {
5754
+ return;
5755
+ }
5756
+ if (ACTIVATION_KEYS.has(event.key)) {
5757
+ event.preventDefault();
5758
+ this.dayClick.emit(date);
5759
+ return;
5760
+ }
5761
+ if (this.focus.move(event.key, date)) {
5762
+ event.preventDefault();
5763
+ }
5764
+ }
5765
+ rootElement() {
5766
+ const root = this.host.nativeElement;
5767
+ return root instanceof HTMLElement ? root : null;
5768
+ }
5769
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCalendarMonthComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5770
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldCalendarMonthComponent, isStandalone: true, selector: "fold-calendar-month", inputs: { month: { classPropertyName: "month", publicName: "month", isSignal: true, isRequired: true, transformFunction: null }, events: { classPropertyName: "events", publicName: "events", isSignal: true, isRequired: false, transformFunction: null }, today: { classPropertyName: "today", publicName: "today", isSignal: true, isRequired: false, transformFunction: null }, weekStartsOn: { classPropertyName: "weekStartsOn", publicName: "weekStartsOn", isSignal: true, isRequired: false, transformFunction: null }, weekendDays: { classPropertyName: "weekendDays", publicName: "weekendDays", isSignal: true, isRequired: false, transformFunction: null }, showWeekNumbers: { classPropertyName: "showWeekNumbers", publicName: "showWeekNumbers", isSignal: true, isRequired: false, transformFunction: null }, maxLanes: { classPropertyName: "maxLanes", publicName: "maxLanes", isSignal: true, isRequired: false, transformFunction: null }, fixedWeeks: { classPropertyName: "fixedWeeks", publicName: "fixedWeeks", isSignal: true, isRequired: false, transformFunction: null }, dayModifiers: { classPropertyName: "dayModifiers", publicName: "dayModifiers", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { month: "monthChange", dayClick: "dayClick", eventClick: "eventClick", overflowClick: "overflowClick" }, host: { attributes: { "data-fold-calendar": "", "role": "grid" }, properties: { "attr.aria-label": "chrome.l().grid", "class.has-week-numbers": "showWeekNumbers()" } }, queries: [{ propertyName: "projectedDay", first: true, predicate: FoldCalendarDayDirective, descendants: true, isSignal: true }, { propertyName: "projectedOverflow", first: true, predicate: FoldCalendarOverflowDirective, descendants: true, isSignal: true }], hostDirectives: [{ directive: FoldCalendarChromeDirective, inputs: ["locale", "locale", "labels", "labels", "formats", "formats"] }], ngImport: i0, template: "<div class=\"foldcal-weekdays\" role=\"row\">\n @if (showWeekNumbers()) {\n <span class=\"foldcal-weekday foldcal-weeknum-head\" role=\"columnheader\">\n {{ chrome.l().weekNumberShort }}\n </span>\n }\n @for (name of weekdayNames(); track $index) {\n <span class=\"foldcal-weekday\" role=\"columnheader\">{{ name }}</span>\n }\n</div>\n\n@for (week of weeks(); track week.start) {\n <div class=\"foldcal-week\" role=\"row\" [style.gridTemplateRows]=\"rowTemplate()\">\n <!--\n ISO weeks always start on Monday and belong to the year holding their\n Thursday, so this is not derivable from the calendar's own anchor.\n -->\n @if (showWeekNumbers()) {\n <span\n class=\"foldcal-weeknum\"\n role=\"rowheader\"\n [attr.aria-label]=\"chrome.l().weekNumber(weekNumber(week))\"\n >{{ weekNumber(week) }}</span\n >\n }\n <!--\n The cell is the focusable widget, as the ARIA grid pattern asks: a\n <button> here would have its native role overwritten by role=\"gridcell\",\n so activation is wired by hand instead.\n -->\n @for (day of week.days; track day.date; let column = $index) {\n <div\n class=\"foldcal-day\"\n role=\"gridcell\"\n [attr.data-fold-day]=\"day.date\"\n [attr.data-fold-day-modifiers]=\"dayModifierAttr(day)\"\n [class.is-outside]=\"!day.inMonth\"\n [class.is-today]=\"day.isToday\"\n [class.is-weekend]=\"day.isWeekend\"\n [style.gridColumn]=\"column + 1 + columnOffset()\"\n [attr.tabindex]=\"dayTabIndex(day.date)\"\n [attr.aria-label]=\"dayLabel(day)\"\n [attr.aria-current]=\"day.isToday ? 'date' : null\"\n (click)=\"dayClick.emit(day.date)\"\n (focus)=\"onDayFocus(day.date)\"\n (keydown)=\"onDayKeydown($event, day.date)\"\n >\n @if (dayContent(); as projected) {\n <ng-container\n [ngTemplateOutlet]=\"projected\"\n [ngTemplateOutletContext]=\"{ $implicit: day }\"\n />\n } @else {\n <span class=\"foldcal-daynum\">{{ day.dayOfMonth }}</span>\n }\n </div>\n }\n\n <!--\n Bands span columns, so they cannot be gridcells; they are hidden from\n the a11y tree and each day cell announces its own event count instead.\n -->\n @for (band of week.bands; track band.key) {\n <button\n type=\"button\"\n class=\"foldcal-band\"\n aria-hidden=\"true\"\n tabindex=\"-1\"\n [attr.data-tone]=\"band.event.tone ?? 'neutral'\"\n [class.continues-before]=\"band.continuesBefore\"\n [class.continues-after]=\"band.continuesAfter\"\n [class.half-start]=\"band.startHalfDay !== undefined\"\n [class.half-end]=\"band.endHalfDay !== undefined\"\n [style.gridColumn]=\"\n band.startColumn +\n 1 +\n columnOffset() +\n ' / ' +\n (band.endColumn + 2 + columnOffset())\n \"\n [style.gridRow]=\"band.lane + 2\"\n [attr.title]=\"bandLabel(band)\"\n (click)=\"eventClick.emit(band.event)\"\n >\n @if (chrome.eventContent(); as projected) {\n <ng-container\n [ngTemplateOutlet]=\"projected\"\n [ngTemplateOutletContext]=\"{ $implicit: band.event, band: band }\"\n />\n } @else {\n <span class=\"foldcal-band-bar\"></span>\n @if (bandIcon(band); as icon) {\n <fold-icon class=\"foldcal-band-icon\" [name]=\"icon\" size=\"sm\" />\n }\n <span class=\"foldcal-band-label\">{{ bandLabel(band) }}</span>\n @if (band.groupSize > 1) {\n <span class=\"foldcal-band-count\">{{ band.groupSize }}</span>\n }\n }\n </button>\n }\n\n <!-- One chip per crowded day, in that day's own column. -->\n @for (day of week.days; track day.date; let column = $index) {\n @if (day.hiddenCount > 0) {\n <button\n type=\"button\"\n class=\"foldcal-overflow\"\n aria-hidden=\"true\"\n tabindex=\"-1\"\n [style.gridColumn]=\"column + 1 + columnOffset()\"\n [style.gridRow]=\"overflowRow()\"\n (click)=\"overflowClick.emit(day.date)\"\n >\n @if (overflowContent(); as projected) {\n <ng-container\n [ngTemplateOutlet]=\"projected\"\n [ngTemplateOutletContext]=\"{\n $implicit: day.hiddenCount,\n date: day.date,\n }\"\n />\n } @else {\n {{ chrome.l().moreEvents(day.hiddenCount) }}\n }\n </button>\n }\n }\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;min-height:0;border:1px solid var(--fold-color-border);border-radius:var(--fold-radius-md);background:var(--fold-color-surface-card);overflow:clip}.foldcal-weekdays,.foldcal-week{grid-template-columns:repeat(7,1fr)}:host(.has-week-numbers) .foldcal-weekdays,:host(.has-week-numbers) .foldcal-week{grid-template-columns:var(--fold-calendar-month-week-number-width, 34px) repeat(7,1fr)}.foldcal-weekdays{display:grid;background:var(--fold-color-surface-subtle);border-bottom:1px solid var(--fold-color-border)}.foldcal-weekday{padding:var(--fold-space-sm) var(--fold-space-md);color:var(--fold-color-text-secondary);font-size:var(--fold-text-xs);font-weight:600;letter-spacing:.08em;text-transform:uppercase}.foldcal-week{display:grid;flex:1;min-height:var(--fold-calendar-month-row-min-height, 112px);border-bottom:1px solid var(--fold-color-border-subtle)}.foldcal-week:last-child{border-bottom:0}.foldcal-weeknum{grid-row:1/-1;display:flex;align-items:flex-start;justify-content:center;padding-top:var(--fold-space-sm);border-inline-end:1px solid var(--fold-color-border-subtle);background:var(--fold-color-surface-subtle);color:var(--fold-color-text-secondary);font-size:var(--fold-text-xs);font-variant-numeric:tabular-nums}.foldcal-weeknum-head{color:var(--fold-color-text-secondary);text-align:center}.foldcal-day{grid-row:1/-1;display:flex;flex-direction:column;align-items:flex-start;padding:var(--fold-calendar-month-cell-padding, var(--fold-space-sm)) var(--fold-calendar-month-cell-padding, var(--fold-space-sm)) var(--fold-space-xs);border-inline-end:1px solid var(--fold-color-border-subtle);color:inherit;font:inherit;text-align:start;cursor:pointer;transition:background-color var(--fold-motion-fast) ease}.foldcal-day:last-of-type{border-inline-end:0}.foldcal-day:hover:not(.is-outside){background:var(--fold-color-surface-hover)}.foldcal-day:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px}.foldcal-day.is-weekend:not(.is-outside){background:var(--fold-color-surface-subtle)}.foldcal-day.is-outside{background:var(--fold-color-surface-sunken)}.foldcal-day.is-outside .foldcal-daynum{color:var(--fold-color-text-secondary)}.foldcal-day.is-today{background:linear-gradient(180deg,var(--fold-color-primary-surface) 0%,transparent 64%);box-shadow:inset 0 2px 0 0 var(--fold-color-primary)}.foldcal-day.is-today .foldcal-daynum{background:var(--fold-color-primary);color:var(--fold-color-on-primary);font-weight:700}.foldcal-daynum{display:inline-flex;align-items:center;justify-content:center;min-width:var(--fold-calendar-month-daynum-size, 22px);height:var(--fold-calendar-month-daynum-size, 22px);padding:0 var(--fold-space-xs);border-radius:var(--fold-radius-sm);color:var(--fold-color-text-secondary);font-size:var(--fold-text-sm);font-variant-numeric:tabular-nums}.foldcal-band{position:relative;z-index:1;display:flex;align-items:center;gap:var(--fold-space-xs);margin-block:1px;margin-inline:var(--fold-calendar-band-gutter, var(--fold-space-xs));padding:0;padding-inline-end:var(--fold-space-sm);border:0;border-radius:var(--fold-calendar-band-radius, var(--fold-radius-sm));background:var(--fold-color-surface-raised);color:var(--fold-color-text);font:inherit;font-size:var(--fold-text-xs);font-weight:500;line-height:var(--fold-calendar-month-lane, 22px);text-align:start;cursor:pointer;overflow:hidden;transition:filter var(--fold-motion-fast) ease,box-shadow var(--fold-motion-fast) ease}.foldcal-band:hover{z-index:2;filter:brightness(1.08);box-shadow:var(--fold-shadow-sm)}.foldcal-band.continues-before{margin-inline-start:0;border-start-start-radius:0;border-end-start-radius:0;padding-inline-start:var(--fold-space-xs)}.foldcal-band.continues-before .foldcal-band-bar{display:none}.foldcal-band.continues-before:before{content:\"\\2039\";flex:none;color:var(--fold-color-text-faded)}.foldcal-band.continues-after{margin-inline-end:0;border-start-end-radius:0;border-end-end-radius:0}.foldcal-band.continues-after:after{content:\"\\203a\";flex:none;color:var(--fold-color-text-faded)}.foldcal-band.half-start{border-end-start-radius:var(--fold-radius-lg)}.foldcal-band.half-end{border-start-end-radius:var(--fold-radius-lg)}[dir=rtl] .foldcal-band.continues-before:before{content:\"\\203a\"}[dir=rtl] .foldcal-band.continues-after:after{content:\"\\2039\"}.foldcal-band-bar{flex:0 0 var(--fold-calendar-bar-width, 3px);align-self:stretch;background:var(--fold-color-text-muted)}.foldcal-band-icon{flex:none;margin-inline-start:2px;opacity:.9}.foldcal-band-label{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.foldcal-band-count{flex:none;padding:0 var(--fold-space-xs);border-radius:var(--fold-radius-pill);background:var(--fold-color-surface-sunken);color:var(--fold-color-text-secondary);font-variant-numeric:tabular-nums}.foldcal-band[data-tone=success]{background:var(--fold-color-success-surface);color:var(--fold-color-success-text)}.foldcal-band[data-tone=success] .foldcal-band-bar{background:var(--fold-color-success)}.foldcal-band[data-tone=warning]{background:var(--fold-color-warning-surface);color:var(--fold-color-warning-text)}.foldcal-band[data-tone=warning] .foldcal-band-bar{background:var(--fold-color-warning)}.foldcal-band[data-tone=alert]{background:var(--fold-color-alert-surface);color:var(--fold-color-alert-text)}.foldcal-band[data-tone=alert] .foldcal-band-bar{background:var(--fold-color-alert)}.foldcal-band[data-tone=muted]{background:transparent;color:var(--fold-color-text-secondary)}.foldcal-band[data-tone=muted] .foldcal-band-bar{background:var(--fold-color-text-faded)}.foldcal-band[data-tone=muted] .foldcal-band-label{color:var(--fold-color-text-secondary);text-decoration:line-through;text-decoration-thickness:1px}@media(forced-colors:active){.foldcal-band{border:1px solid CanvasText}.foldcal-band-bar{forced-color-adjust:none}}.foldcal-overflow{justify-self:start;align-self:start;z-index:1;margin-inline:var(--fold-calendar-band-gutter, var(--fold-space-xs));padding:0 var(--fold-space-xs);border:0;border-radius:var(--fold-calendar-band-radius, var(--fold-radius-sm));background:transparent;color:var(--fold-color-text-secondary);font:inherit;font-size:var(--fold-text-xs);font-weight:500;cursor:pointer}.foldcal-overflow:hover{color:var(--fold-color-text);background:var(--fold-color-surface-hover)}@media(forced-colors:active){.foldcal-day.is-today{outline:2px solid Highlight;outline-offset:-2px}.foldcal-day:focus-visible{outline-color:Highlight}.foldcal-day.is-outside .foldcal-daynum{opacity:.6}.foldcal-day.is-weekend{border-block-end:2px solid GrayText}}@media(prefers-reduced-motion:reduce){.foldcal-day,.foldcal-band{transition:none}}@media print{:host{border-color:CanvasText;background:Canvas}.foldcal-weekdays,.foldcal-weekday,.foldcal-day,.foldcal-daynum,.foldcal-weeknum,.foldcal-overflow{background:Canvas;color:CanvasText}.foldcal-week,.foldcal-day,.foldcal-weeknum{border-color:GrayText}.foldcal-day.is-today{background:Canvas;box-shadow:inset 0 2px 0 0 CanvasText}.foldcal-day.is-today .foldcal-daynum{font-weight:700;text-decoration:underline}.foldcal-day.is-outside{opacity:.55}.foldcal-band{border:1px solid GrayText;background:Canvas;color:CanvasText;box-shadow:none}.foldcal-band-bar{-webkit-print-color-adjust:exact;print-color-adjust:exact}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: FoldIconComponent, selector: "fold-icon", inputs: ["name", "size", "tone", "title"] }] });
5771
+ }
5772
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCalendarMonthComponent, decorators: [{
5773
+ type: Component,
5774
+ args: [{ selector: "fold-calendar-month", standalone: true, imports: [NgTemplateOutlet, FoldIconComponent], hostDirectives: [
5775
+ {
5776
+ directive: FoldCalendarChromeDirective,
5777
+ inputs: ["locale", "labels", "formats"],
5778
+ },
5779
+ ], host: {
5780
+ "data-fold-calendar": "",
5781
+ role: "grid",
5782
+ "[attr.aria-label]": "chrome.l().grid",
5783
+ "[class.has-week-numbers]": "showWeekNumbers()",
5784
+ }, template: "<div class=\"foldcal-weekdays\" role=\"row\">\n @if (showWeekNumbers()) {\n <span class=\"foldcal-weekday foldcal-weeknum-head\" role=\"columnheader\">\n {{ chrome.l().weekNumberShort }}\n </span>\n }\n @for (name of weekdayNames(); track $index) {\n <span class=\"foldcal-weekday\" role=\"columnheader\">{{ name }}</span>\n }\n</div>\n\n@for (week of weeks(); track week.start) {\n <div class=\"foldcal-week\" role=\"row\" [style.gridTemplateRows]=\"rowTemplate()\">\n <!--\n ISO weeks always start on Monday and belong to the year holding their\n Thursday, so this is not derivable from the calendar's own anchor.\n -->\n @if (showWeekNumbers()) {\n <span\n class=\"foldcal-weeknum\"\n role=\"rowheader\"\n [attr.aria-label]=\"chrome.l().weekNumber(weekNumber(week))\"\n >{{ weekNumber(week) }}</span\n >\n }\n <!--\n The cell is the focusable widget, as the ARIA grid pattern asks: a\n <button> here would have its native role overwritten by role=\"gridcell\",\n so activation is wired by hand instead.\n -->\n @for (day of week.days; track day.date; let column = $index) {\n <div\n class=\"foldcal-day\"\n role=\"gridcell\"\n [attr.data-fold-day]=\"day.date\"\n [attr.data-fold-day-modifiers]=\"dayModifierAttr(day)\"\n [class.is-outside]=\"!day.inMonth\"\n [class.is-today]=\"day.isToday\"\n [class.is-weekend]=\"day.isWeekend\"\n [style.gridColumn]=\"column + 1 + columnOffset()\"\n [attr.tabindex]=\"dayTabIndex(day.date)\"\n [attr.aria-label]=\"dayLabel(day)\"\n [attr.aria-current]=\"day.isToday ? 'date' : null\"\n (click)=\"dayClick.emit(day.date)\"\n (focus)=\"onDayFocus(day.date)\"\n (keydown)=\"onDayKeydown($event, day.date)\"\n >\n @if (dayContent(); as projected) {\n <ng-container\n [ngTemplateOutlet]=\"projected\"\n [ngTemplateOutletContext]=\"{ $implicit: day }\"\n />\n } @else {\n <span class=\"foldcal-daynum\">{{ day.dayOfMonth }}</span>\n }\n </div>\n }\n\n <!--\n Bands span columns, so they cannot be gridcells; they are hidden from\n the a11y tree and each day cell announces its own event count instead.\n -->\n @for (band of week.bands; track band.key) {\n <button\n type=\"button\"\n class=\"foldcal-band\"\n aria-hidden=\"true\"\n tabindex=\"-1\"\n [attr.data-tone]=\"band.event.tone ?? 'neutral'\"\n [class.continues-before]=\"band.continuesBefore\"\n [class.continues-after]=\"band.continuesAfter\"\n [class.half-start]=\"band.startHalfDay !== undefined\"\n [class.half-end]=\"band.endHalfDay !== undefined\"\n [style.gridColumn]=\"\n band.startColumn +\n 1 +\n columnOffset() +\n ' / ' +\n (band.endColumn + 2 + columnOffset())\n \"\n [style.gridRow]=\"band.lane + 2\"\n [attr.title]=\"bandLabel(band)\"\n (click)=\"eventClick.emit(band.event)\"\n >\n @if (chrome.eventContent(); as projected) {\n <ng-container\n [ngTemplateOutlet]=\"projected\"\n [ngTemplateOutletContext]=\"{ $implicit: band.event, band: band }\"\n />\n } @else {\n <span class=\"foldcal-band-bar\"></span>\n @if (bandIcon(band); as icon) {\n <fold-icon class=\"foldcal-band-icon\" [name]=\"icon\" size=\"sm\" />\n }\n <span class=\"foldcal-band-label\">{{ bandLabel(band) }}</span>\n @if (band.groupSize > 1) {\n <span class=\"foldcal-band-count\">{{ band.groupSize }}</span>\n }\n }\n </button>\n }\n\n <!-- One chip per crowded day, in that day's own column. -->\n @for (day of week.days; track day.date; let column = $index) {\n @if (day.hiddenCount > 0) {\n <button\n type=\"button\"\n class=\"foldcal-overflow\"\n aria-hidden=\"true\"\n tabindex=\"-1\"\n [style.gridColumn]=\"column + 1 + columnOffset()\"\n [style.gridRow]=\"overflowRow()\"\n (click)=\"overflowClick.emit(day.date)\"\n >\n @if (overflowContent(); as projected) {\n <ng-container\n [ngTemplateOutlet]=\"projected\"\n [ngTemplateOutletContext]=\"{\n $implicit: day.hiddenCount,\n date: day.date,\n }\"\n />\n } @else {\n {{ chrome.l().moreEvents(day.hiddenCount) }}\n }\n </button>\n }\n }\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;min-height:0;border:1px solid var(--fold-color-border);border-radius:var(--fold-radius-md);background:var(--fold-color-surface-card);overflow:clip}.foldcal-weekdays,.foldcal-week{grid-template-columns:repeat(7,1fr)}:host(.has-week-numbers) .foldcal-weekdays,:host(.has-week-numbers) .foldcal-week{grid-template-columns:var(--fold-calendar-month-week-number-width, 34px) repeat(7,1fr)}.foldcal-weekdays{display:grid;background:var(--fold-color-surface-subtle);border-bottom:1px solid var(--fold-color-border)}.foldcal-weekday{padding:var(--fold-space-sm) var(--fold-space-md);color:var(--fold-color-text-secondary);font-size:var(--fold-text-xs);font-weight:600;letter-spacing:.08em;text-transform:uppercase}.foldcal-week{display:grid;flex:1;min-height:var(--fold-calendar-month-row-min-height, 112px);border-bottom:1px solid var(--fold-color-border-subtle)}.foldcal-week:last-child{border-bottom:0}.foldcal-weeknum{grid-row:1/-1;display:flex;align-items:flex-start;justify-content:center;padding-top:var(--fold-space-sm);border-inline-end:1px solid var(--fold-color-border-subtle);background:var(--fold-color-surface-subtle);color:var(--fold-color-text-secondary);font-size:var(--fold-text-xs);font-variant-numeric:tabular-nums}.foldcal-weeknum-head{color:var(--fold-color-text-secondary);text-align:center}.foldcal-day{grid-row:1/-1;display:flex;flex-direction:column;align-items:flex-start;padding:var(--fold-calendar-month-cell-padding, var(--fold-space-sm)) var(--fold-calendar-month-cell-padding, var(--fold-space-sm)) var(--fold-space-xs);border-inline-end:1px solid var(--fold-color-border-subtle);color:inherit;font:inherit;text-align:start;cursor:pointer;transition:background-color var(--fold-motion-fast) ease}.foldcal-day:last-of-type{border-inline-end:0}.foldcal-day:hover:not(.is-outside){background:var(--fold-color-surface-hover)}.foldcal-day:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px}.foldcal-day.is-weekend:not(.is-outside){background:var(--fold-color-surface-subtle)}.foldcal-day.is-outside{background:var(--fold-color-surface-sunken)}.foldcal-day.is-outside .foldcal-daynum{color:var(--fold-color-text-secondary)}.foldcal-day.is-today{background:linear-gradient(180deg,var(--fold-color-primary-surface) 0%,transparent 64%);box-shadow:inset 0 2px 0 0 var(--fold-color-primary)}.foldcal-day.is-today .foldcal-daynum{background:var(--fold-color-primary);color:var(--fold-color-on-primary);font-weight:700}.foldcal-daynum{display:inline-flex;align-items:center;justify-content:center;min-width:var(--fold-calendar-month-daynum-size, 22px);height:var(--fold-calendar-month-daynum-size, 22px);padding:0 var(--fold-space-xs);border-radius:var(--fold-radius-sm);color:var(--fold-color-text-secondary);font-size:var(--fold-text-sm);font-variant-numeric:tabular-nums}.foldcal-band{position:relative;z-index:1;display:flex;align-items:center;gap:var(--fold-space-xs);margin-block:1px;margin-inline:var(--fold-calendar-band-gutter, var(--fold-space-xs));padding:0;padding-inline-end:var(--fold-space-sm);border:0;border-radius:var(--fold-calendar-band-radius, var(--fold-radius-sm));background:var(--fold-color-surface-raised);color:var(--fold-color-text);font:inherit;font-size:var(--fold-text-xs);font-weight:500;line-height:var(--fold-calendar-month-lane, 22px);text-align:start;cursor:pointer;overflow:hidden;transition:filter var(--fold-motion-fast) ease,box-shadow var(--fold-motion-fast) ease}.foldcal-band:hover{z-index:2;filter:brightness(1.08);box-shadow:var(--fold-shadow-sm)}.foldcal-band.continues-before{margin-inline-start:0;border-start-start-radius:0;border-end-start-radius:0;padding-inline-start:var(--fold-space-xs)}.foldcal-band.continues-before .foldcal-band-bar{display:none}.foldcal-band.continues-before:before{content:\"\\2039\";flex:none;color:var(--fold-color-text-faded)}.foldcal-band.continues-after{margin-inline-end:0;border-start-end-radius:0;border-end-end-radius:0}.foldcal-band.continues-after:after{content:\"\\203a\";flex:none;color:var(--fold-color-text-faded)}.foldcal-band.half-start{border-end-start-radius:var(--fold-radius-lg)}.foldcal-band.half-end{border-start-end-radius:var(--fold-radius-lg)}[dir=rtl] .foldcal-band.continues-before:before{content:\"\\203a\"}[dir=rtl] .foldcal-band.continues-after:after{content:\"\\2039\"}.foldcal-band-bar{flex:0 0 var(--fold-calendar-bar-width, 3px);align-self:stretch;background:var(--fold-color-text-muted)}.foldcal-band-icon{flex:none;margin-inline-start:2px;opacity:.9}.foldcal-band-label{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.foldcal-band-count{flex:none;padding:0 var(--fold-space-xs);border-radius:var(--fold-radius-pill);background:var(--fold-color-surface-sunken);color:var(--fold-color-text-secondary);font-variant-numeric:tabular-nums}.foldcal-band[data-tone=success]{background:var(--fold-color-success-surface);color:var(--fold-color-success-text)}.foldcal-band[data-tone=success] .foldcal-band-bar{background:var(--fold-color-success)}.foldcal-band[data-tone=warning]{background:var(--fold-color-warning-surface);color:var(--fold-color-warning-text)}.foldcal-band[data-tone=warning] .foldcal-band-bar{background:var(--fold-color-warning)}.foldcal-band[data-tone=alert]{background:var(--fold-color-alert-surface);color:var(--fold-color-alert-text)}.foldcal-band[data-tone=alert] .foldcal-band-bar{background:var(--fold-color-alert)}.foldcal-band[data-tone=muted]{background:transparent;color:var(--fold-color-text-secondary)}.foldcal-band[data-tone=muted] .foldcal-band-bar{background:var(--fold-color-text-faded)}.foldcal-band[data-tone=muted] .foldcal-band-label{color:var(--fold-color-text-secondary);text-decoration:line-through;text-decoration-thickness:1px}@media(forced-colors:active){.foldcal-band{border:1px solid CanvasText}.foldcal-band-bar{forced-color-adjust:none}}.foldcal-overflow{justify-self:start;align-self:start;z-index:1;margin-inline:var(--fold-calendar-band-gutter, var(--fold-space-xs));padding:0 var(--fold-space-xs);border:0;border-radius:var(--fold-calendar-band-radius, var(--fold-radius-sm));background:transparent;color:var(--fold-color-text-secondary);font:inherit;font-size:var(--fold-text-xs);font-weight:500;cursor:pointer}.foldcal-overflow:hover{color:var(--fold-color-text);background:var(--fold-color-surface-hover)}@media(forced-colors:active){.foldcal-day.is-today{outline:2px solid Highlight;outline-offset:-2px}.foldcal-day:focus-visible{outline-color:Highlight}.foldcal-day.is-outside .foldcal-daynum{opacity:.6}.foldcal-day.is-weekend{border-block-end:2px solid GrayText}}@media(prefers-reduced-motion:reduce){.foldcal-day,.foldcal-band{transition:none}}@media print{:host{border-color:CanvasText;background:Canvas}.foldcal-weekdays,.foldcal-weekday,.foldcal-day,.foldcal-daynum,.foldcal-weeknum,.foldcal-overflow{background:Canvas;color:CanvasText}.foldcal-week,.foldcal-day,.foldcal-weeknum{border-color:GrayText}.foldcal-day.is-today{background:Canvas;box-shadow:inset 0 2px 0 0 CanvasText}.foldcal-day.is-today .foldcal-daynum{font-weight:700;text-decoration:underline}.foldcal-day.is-outside{opacity:.55}.foldcal-band{border:1px solid GrayText;background:Canvas;color:CanvasText;box-shadow:none}.foldcal-band-bar{-webkit-print-color-adjust:exact;print-color-adjust:exact}}\n"] }]
5785
+ }], propDecorators: { month: [{ type: i0.Input, args: [{ isSignal: true, alias: "month", required: true }] }, { type: i0.Output, args: ["monthChange"] }], events: [{ type: i0.Input, args: [{ isSignal: true, alias: "events", required: false }] }], today: [{ type: i0.Input, args: [{ isSignal: true, alias: "today", required: false }] }], weekStartsOn: [{ type: i0.Input, args: [{ isSignal: true, alias: "weekStartsOn", required: false }] }], weekendDays: [{ type: i0.Input, args: [{ isSignal: true, alias: "weekendDays", required: false }] }], showWeekNumbers: [{ type: i0.Input, args: [{ isSignal: true, alias: "showWeekNumbers", required: false }] }], maxLanes: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLanes", required: false }] }], fixedWeeks: [{ type: i0.Input, args: [{ isSignal: true, alias: "fixedWeeks", required: false }] }], dayModifiers: [{ type: i0.Input, args: [{ isSignal: true, alias: "dayModifiers", required: false }] }], dayClick: [{ type: i0.Output, args: ["dayClick"] }], eventClick: [{ type: i0.Output, args: ["eventClick"] }], overflowClick: [{ type: i0.Output, args: ["overflowClick"] }], projectedDay: [{ type: i0.ContentChild, args: [i0.forwardRef(() => FoldCalendarDayDirective), { isSignal: true }] }], projectedOverflow: [{ type: i0.ContentChild, args: [i0.forwardRef(() => FoldCalendarOverflowDirective), { isSignal: true }] }] } });
5786
+
5787
+ /**
5788
+ * The three selections every view and every consumer makes over a feed.
5789
+ *
5790
+ * Deliberately the smallest, least coupled part of the family: an app that
5791
+ * wants the calendar's semantics — "covers this day", "overlaps this window",
5792
+ * "belongs to a live feed" — without any of its rendering imports only this.
5793
+ */
5794
+ /**
5795
+ * Events covering `date`, in the caller's order — the selection the week, day
5796
+ * and list views render, where chips stack in a column and need no packing.
5797
+ */
5798
+ function foldEventsOnDay(events, date) {
5799
+ return events.filter((event) => event.start <= date && event.end >= date);
5800
+ }
5801
+ /** Events overlapping `[from, to]` at all, in the caller's order. */
5802
+ function foldEventsInRange(events, from, to) {
5803
+ return events.filter((event) => event.end >= from && event.start <= to);
5804
+ }
5805
+ /**
5806
+ * Events from the feeds in `active`, in the caller's order — the other half of
5807
+ * `fold-calendar-source-filter`, which owns only the selection.
5808
+ *
5809
+ * `null` means nothing has been switched off yet and everything passes; it is
5810
+ * the filter chips' own initial value, so the one function built to consume
5811
+ * that model accepts it rather than making every caller write the ternary.
5812
+ * An empty `Set` is the opposite — every feed switched off — which is why the
5813
+ * two are not collapsed.
5814
+ *
5815
+ * An event with no `sourceKey` belongs to no feed, so no chip can hide it and
5816
+ * it always passes.
5817
+ */
5818
+ function foldFilterBySource(events, active) {
5819
+ if (active === null) {
5820
+ return events;
5821
+ }
5822
+ return events.filter((event) => event.sourceKey === undefined || active.has(event.sourceKey));
5823
+ }
5824
+
5825
+ /** Columns in a week row. */
5826
+ const DAYS_PER_WEEK = 7;
5827
+ /** One day cell plus the events covering it. */
5828
+ function dayWithEvents(events, epochDay, month, options, counts) {
5829
+ const day = foldMakeDay(epochDay, {
5830
+ month,
5831
+ today: options.today,
5832
+ weekendDays: foldWeekendOr(options.weekendDays),
5833
+ counts,
5834
+ hidden: FOLD_NO_HIDDEN_DAYS,
5835
+ });
5836
+ return { day, events: foldEventsOnDay(events, day.date) };
5837
+ }
5838
+ /**
5839
+ * The seven days of `date`'s week, each with the events covering it — the
5840
+ * render model of the week view.
5841
+ */
5842
+ function foldBuildWeek(events, options) {
5843
+ const startDay = foldToEpochDay(foldStartOfWeek(options.date, options.weekStartsOn ?? "mon"));
5844
+ const month = options.date.slice(0, 7);
5845
+ const counts = foldCountByDay(events, startDay, startDay + DAYS_PER_WEEK - 1);
5846
+ return Array.from({ length: DAYS_PER_WEEK }, (_unused, offset) => dayWithEvents(events, startDay + offset, month, options, counts));
5847
+ }
5848
+ /** A single day with its events — the render model of the day view. */
5849
+ function foldBuildDay(events, options) {
5850
+ const day = foldToEpochDay(options.date);
5851
+ return dayWithEvents(events, day, options.date.slice(0, 7), options, foldCountByDay(events, day, day));
5852
+ }
5853
+
5854
+ /**
5855
+ * `<fold-calendar-week>` — one week as seven day columns of stacked chips.
5856
+ *
5857
+ * Where the month view packs spans into lanes, a week simply lists what covers
5858
+ * each day: a three-day event appears under all three, which is the reading a
5859
+ * week is for. Nothing spans, so nothing is clipped and there is no overflow.
5860
+ *
5861
+ * ## Inputs
5862
+ * | Input | Type | Default | Meaning |
5863
+ * |----------------|--------------------------|---------|---------|
5864
+ * | `date` | `FoldCalendarDate` | — | Any date in the week on display. |
5865
+ * | `events` | `FoldCalendarEvent<T>[]` | `[]` | What to list. |
5866
+ * | `today` | `FoldCalendarDate` | — | The day to mark. |
5867
+ * | `weekStartsOn` | `FoldWeekday` | the locale's | Which day opens the week — `Intl.Locale` knows. |
5868
+ * | `weekendDays` | `FoldWeekday[]` | the locale's | Which days are shaded. |
5869
+ * | `locale` | `string` | runtime | Drives the column headers through `Intl`. |
5870
+ * | `labels` | `Partial<FoldCalendarLabels>` | — | Per-instance label overrides. |
5871
+ *
5872
+ * `date` is a plain input, not a `model`: this view never pages itself, so
5873
+ * writing back would be a promise it does not keep. Pair it with
5874
+ * `<fold-calendar-toolbar>`, which does own the paging.
5875
+ *
5876
+ * ## Theming
5877
+ * | CSS variable | Default | Sets |
5878
+ * |---------------------------------------|---------|------|
5879
+ * | `--fold-calendar-week-column-height` | `320px` | Height of the day columns. |
5880
+ * | `--fold-calendar-bar-width` | `3px` | The tone bar down a chip's leading edge. |
5881
+ * | `--fold-calendar-band-radius` | `--fold-radius-sm` | Chip corners. |
5882
+ *
5883
+ * ## Outputs
5884
+ * | Output | Payload | Fires on |
5885
+ * |--------------|------------------------|----------|
5886
+ * | `dayClick` | `FoldCalendarDate` | A column header activated. |
5887
+ * | `eventClick` | `FoldCalendarEvent<T>` | A chip activated. |
5888
+ *
5889
+ * ## Accessibility
5890
+ * Each column is a `<section>` named by its full date, holding a header button
5891
+ * and a list of chips. Because a chip sits **inside** the day it belongs to —
5892
+ * no spanning, no overlay — every control is a real child in the natural tab
5893
+ * order, so this view needs none of the month grid's roving-tabindex machinery
5894
+ * and every event is reachable by keyboard.
5895
+ *
5896
+ * @selector `fold-calendar-week`
5897
+ *
5898
+ * @example
5899
+ * ```html
5900
+ * <fold-calendar-week
5901
+ * [(date)]="date"
5902
+ * [events]="events()"
5903
+ * [today]="today"
5904
+ * (eventClick)="open($event)"
5905
+ * />
5906
+ * ```
5907
+ */
5908
+ class FoldCalendarWeekComponent {
5909
+ /** Any date inside the week on display. */
5910
+ date = input.required(/* @ts-ignore */
5911
+ ...(ngDevMode ? [{ debugName: "date" }] : /* istanbul ignore next */ []));
5912
+ /** Events to list under the days they cover. */
5913
+ events = input([], /* @ts-ignore */
5914
+ ...(ngDevMode ? [{ debugName: "events" }] : /* istanbul ignore next */ []));
5915
+ /** The day to mark as today. */
5916
+ today = input(/* @ts-ignore */
5917
+ ...(ngDevMode ? [undefined, { debugName: "today" }] : /* istanbul ignore next */ []));
5918
+ /** Day the week starts on. @default the locale's own first day */
5919
+ weekStartsOn = input(/* @ts-ignore */
5920
+ ...(ngDevMode ? [undefined, { debugName: "weekStartsOn" }] : /* istanbul ignore next */ []));
5921
+ /** Days shaded as the weekend. @default ['sat', 'sun'] */
5922
+ weekendDays = input(/* @ts-ignore */
5923
+ ...(ngDevMode ? [undefined, { debugName: "weekendDays" }] : /* istanbul ignore next */ []));
5924
+ /** A column header was activated. */
5925
+ dayClick = output();
5926
+ /** A chip was activated. */
5927
+ eventClick = output();
5928
+ /** Labels, locale and the projected chip template — see the directive. */
5929
+ chrome = inject(FoldCalendarChromeDirective);
5930
+ columns = computed(() => foldBuildWeek(this.events(), {
5931
+ date: this.date(),
5932
+ weekStartsOn: this.chrome.anchor(this.weekStartsOn()),
5933
+ weekendDays: this.chrome.weekend(this.weekendDays()),
5934
+ today: this.today(),
5935
+ }), /* @ts-ignore */
5936
+ ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
5937
+ weekdayName(column) {
5938
+ return this.chrome.format(column.day.date, "weekdayShort");
5939
+ }
5940
+ /** A column's accessible name: its date, the today marker, and what sits on it. */
5941
+ columnLabel(column) {
5942
+ const labels = this.chrome.l();
5943
+ const parts = [this.chrome.format(column.day.date, "dateFull")];
5944
+ if (column.day.isToday) {
5945
+ parts.push(labels.today);
5946
+ }
5947
+ if (column.day.eventCount > 0) {
5948
+ parts.push(labels.eventCount(column.day.eventCount));
5949
+ }
5950
+ return parts.join(", ");
5951
+ }
5952
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCalendarWeekComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5953
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldCalendarWeekComponent, isStandalone: true, selector: "fold-calendar-week", inputs: { date: { classPropertyName: "date", publicName: "date", isSignal: true, isRequired: true, transformFunction: null }, events: { classPropertyName: "events", publicName: "events", isSignal: true, isRequired: false, transformFunction: null }, today: { classPropertyName: "today", publicName: "today", isSignal: true, isRequired: false, transformFunction: null }, weekStartsOn: { classPropertyName: "weekStartsOn", publicName: "weekStartsOn", isSignal: true, isRequired: false, transformFunction: null }, weekendDays: { classPropertyName: "weekendDays", publicName: "weekendDays", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dayClick: "dayClick", eventClick: "eventClick" }, hostDirectives: [{ directive: FoldCalendarChromeDirective, inputs: ["locale", "locale", "labels", "labels", "formats", "formats"] }], ngImport: i0, template: "<div class=\"foldcalw\" [attr.aria-label]=\"chrome.l().grid\" role=\"group\">\n @for (column of columns(); track column.day.date) {\n <section\n class=\"foldcalw-col\"\n [class.is-today]=\"column.day.isToday\"\n [class.is-weekend]=\"column.day.isWeekend\"\n [attr.aria-label]=\"columnLabel(column)\"\n >\n <button\n type=\"button\"\n class=\"foldcalw-head\"\n [attr.aria-current]=\"column.day.isToday ? 'date' : null\"\n (click)=\"dayClick.emit(column.day.date)\"\n >\n <span class=\"foldcalw-weekday\">{{ weekdayName(column) }}</span>\n <span class=\"foldcalw-daynum\">{{ column.day.dayOfMonth }}</span>\n </button>\n\n <ul class=\"foldcalw-list\">\n @for (event of column.events; track event.id) {\n <li>\n <button\n type=\"button\"\n class=\"foldcal-chip\"\n [attr.data-tone]=\"event.tone ?? 'neutral'\"\n (click)=\"eventClick.emit(event)\"\n >\n @if (chrome.eventContent(); as projected) {\n <ng-container\n [ngTemplateOutlet]=\"projected\"\n [ngTemplateOutletContext]=\"{ $implicit: event, band: null }\"\n />\n } @else {\n <span class=\"foldcal-chip-bar\"></span>\n @if (event.icon; as icon) {\n <fold-icon\n class=\"foldcal-chip-icon\"\n [name]=\"icon\"\n size=\"sm\"\n />\n }\n <span class=\"foldcal-chip-body\">\n <span class=\"foldcal-chip-label\">{{ event.label }}</span>\n @if (event.subline) {\n <span class=\"foldcal-chip-sub\">{{ event.subline }}</span>\n }\n </span>\n }\n </button>\n </li>\n }\n </ul>\n </section>\n }\n</div>\n", styles: [":host{display:block}.foldcal-chip{position:relative;display:flex;align-items:stretch;gap:var(--fold-space-sm);width:100%;padding:var(--fold-space-xs) var(--fold-space-sm);padding-inline-start:0;border:0;border-radius:var(--fold-calendar-band-radius, var(--fold-radius-sm));background:var(--fold-color-surface-raised);color:var(--fold-color-text);font:inherit;text-align:start;cursor:pointer;overflow:hidden;transition:background-color var(--fold-motion-fast) ease}.foldcal-chip:hover{background:var(--fold-color-surface-hover)}.foldcal-chip:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px}.foldcal-chip-bar{flex:0 0 var(--fold-calendar-bar-width, 3px);align-self:stretch;background:var(--fold-color-text-muted)}.foldcal-chip-icon{flex:none;align-self:center;margin-inline-start:var(--fold-space-sm);color:var(--fold-color-text-secondary)}.foldcal-chip-body{display:flex;flex-direction:column;gap:1px;min-width:0}.foldcal-chip-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--fold-text-sm);font-weight:500}.foldcal-chip-sub{color:var(--fold-color-text-secondary);font-size:var(--fold-text-xs)}.foldcal-chip[data-tone=success] .foldcal-chip-bar{background:var(--fold-color-success)}.foldcal-chip[data-tone=warning] .foldcal-chip-bar{background:var(--fold-color-warning)}.foldcal-chip[data-tone=alert] .foldcal-chip-bar{background:var(--fold-color-alert)}.foldcal-chip[data-tone=muted] .foldcal-chip-bar{background:var(--fold-color-text-faded)}.foldcal-chip[data-tone=muted] .foldcal-chip-label{color:var(--fold-color-text-secondary);text-decoration:line-through;text-decoration-thickness:1px}@media(forced-colors:active){.foldcal-chip{border:1px solid CanvasText}.foldcal-chip-bar{forced-color-adjust:none}}@media(prefers-reduced-motion:reduce){.foldcal-chip{transition:none}}.foldcalw{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));min-height:var(--fold-calendar-week-column-height, 320px);border:1px solid var(--fold-color-border);border-radius:var(--fold-radius-md);background:var(--fold-color-surface-card);overflow:clip}.foldcalw-col{container-type:inline-size;display:flex;flex-direction:column;gap:var(--fold-space-sm);min-width:0;padding:var(--fold-space-sm);border-inline-end:1px solid var(--fold-color-border-subtle)}.foldcalw-col:last-child{border-inline-end:0}.foldcalw-col.is-weekend{background:var(--fold-color-surface-subtle)}.foldcalw-col.is-today{background:linear-gradient(180deg,var(--fold-color-primary-surface) 0%,transparent 28%);box-shadow:inset 0 2px 0 0 var(--fold-color-primary)}.foldcalw-head{display:flex;flex-direction:column;align-items:flex-start;gap:1px;padding:0 0 var(--fold-space-xs);border:0;border-bottom:1px solid var(--fold-color-border-subtle);background:transparent;color:inherit;font:inherit;text-align:start;cursor:pointer}.foldcalw-head:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:2px}.foldcalw-weekday{color:var(--fold-color-text-secondary);font-size:var(--fold-text-xs);font-weight:600;letter-spacing:.06em;text-transform:uppercase}.foldcalw-daynum{color:var(--fold-color-text);font-size:var(--fold-text-lg);font-weight:600;font-variant-numeric:tabular-nums;line-height:1}.is-today .foldcalw-daynum{color:var(--fold-color-primary-text)}.foldcalw-list{display:flex;flex-direction:column;gap:var(--fold-space-xs);margin:0;padding:0;list-style:none}@container (max-width: 150px){.foldcal-chip-sub{display:none}}@container (max-width: 110px){.foldcal-chip-icon{display:none}.foldcal-chip{gap:var(--fold-space-xs);padding-inline-end:var(--fold-space-xs)}}@media(forced-colors:active){.foldcalw-col.is-today{outline:2px solid Highlight;outline-offset:-2px}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: FoldIconComponent, selector: "fold-icon", inputs: ["name", "size", "tone", "title"] }] });
5954
+ }
5955
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCalendarWeekComponent, decorators: [{
5956
+ type: Component,
5957
+ args: [{ selector: "fold-calendar-week", standalone: true, imports: [NgTemplateOutlet, FoldIconComponent], hostDirectives: [
5958
+ {
5959
+ directive: FoldCalendarChromeDirective,
5960
+ inputs: ["locale", "labels", "formats"],
5961
+ },
5962
+ ], template: "<div class=\"foldcalw\" [attr.aria-label]=\"chrome.l().grid\" role=\"group\">\n @for (column of columns(); track column.day.date) {\n <section\n class=\"foldcalw-col\"\n [class.is-today]=\"column.day.isToday\"\n [class.is-weekend]=\"column.day.isWeekend\"\n [attr.aria-label]=\"columnLabel(column)\"\n >\n <button\n type=\"button\"\n class=\"foldcalw-head\"\n [attr.aria-current]=\"column.day.isToday ? 'date' : null\"\n (click)=\"dayClick.emit(column.day.date)\"\n >\n <span class=\"foldcalw-weekday\">{{ weekdayName(column) }}</span>\n <span class=\"foldcalw-daynum\">{{ column.day.dayOfMonth }}</span>\n </button>\n\n <ul class=\"foldcalw-list\">\n @for (event of column.events; track event.id) {\n <li>\n <button\n type=\"button\"\n class=\"foldcal-chip\"\n [attr.data-tone]=\"event.tone ?? 'neutral'\"\n (click)=\"eventClick.emit(event)\"\n >\n @if (chrome.eventContent(); as projected) {\n <ng-container\n [ngTemplateOutlet]=\"projected\"\n [ngTemplateOutletContext]=\"{ $implicit: event, band: null }\"\n />\n } @else {\n <span class=\"foldcal-chip-bar\"></span>\n @if (event.icon; as icon) {\n <fold-icon\n class=\"foldcal-chip-icon\"\n [name]=\"icon\"\n size=\"sm\"\n />\n }\n <span class=\"foldcal-chip-body\">\n <span class=\"foldcal-chip-label\">{{ event.label }}</span>\n @if (event.subline) {\n <span class=\"foldcal-chip-sub\">{{ event.subline }}</span>\n }\n </span>\n }\n </button>\n </li>\n }\n </ul>\n </section>\n }\n</div>\n", styles: [":host{display:block}.foldcal-chip{position:relative;display:flex;align-items:stretch;gap:var(--fold-space-sm);width:100%;padding:var(--fold-space-xs) var(--fold-space-sm);padding-inline-start:0;border:0;border-radius:var(--fold-calendar-band-radius, var(--fold-radius-sm));background:var(--fold-color-surface-raised);color:var(--fold-color-text);font:inherit;text-align:start;cursor:pointer;overflow:hidden;transition:background-color var(--fold-motion-fast) ease}.foldcal-chip:hover{background:var(--fold-color-surface-hover)}.foldcal-chip:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px}.foldcal-chip-bar{flex:0 0 var(--fold-calendar-bar-width, 3px);align-self:stretch;background:var(--fold-color-text-muted)}.foldcal-chip-icon{flex:none;align-self:center;margin-inline-start:var(--fold-space-sm);color:var(--fold-color-text-secondary)}.foldcal-chip-body{display:flex;flex-direction:column;gap:1px;min-width:0}.foldcal-chip-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--fold-text-sm);font-weight:500}.foldcal-chip-sub{color:var(--fold-color-text-secondary);font-size:var(--fold-text-xs)}.foldcal-chip[data-tone=success] .foldcal-chip-bar{background:var(--fold-color-success)}.foldcal-chip[data-tone=warning] .foldcal-chip-bar{background:var(--fold-color-warning)}.foldcal-chip[data-tone=alert] .foldcal-chip-bar{background:var(--fold-color-alert)}.foldcal-chip[data-tone=muted] .foldcal-chip-bar{background:var(--fold-color-text-faded)}.foldcal-chip[data-tone=muted] .foldcal-chip-label{color:var(--fold-color-text-secondary);text-decoration:line-through;text-decoration-thickness:1px}@media(forced-colors:active){.foldcal-chip{border:1px solid CanvasText}.foldcal-chip-bar{forced-color-adjust:none}}@media(prefers-reduced-motion:reduce){.foldcal-chip{transition:none}}.foldcalw{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));min-height:var(--fold-calendar-week-column-height, 320px);border:1px solid var(--fold-color-border);border-radius:var(--fold-radius-md);background:var(--fold-color-surface-card);overflow:clip}.foldcalw-col{container-type:inline-size;display:flex;flex-direction:column;gap:var(--fold-space-sm);min-width:0;padding:var(--fold-space-sm);border-inline-end:1px solid var(--fold-color-border-subtle)}.foldcalw-col:last-child{border-inline-end:0}.foldcalw-col.is-weekend{background:var(--fold-color-surface-subtle)}.foldcalw-col.is-today{background:linear-gradient(180deg,var(--fold-color-primary-surface) 0%,transparent 28%);box-shadow:inset 0 2px 0 0 var(--fold-color-primary)}.foldcalw-head{display:flex;flex-direction:column;align-items:flex-start;gap:1px;padding:0 0 var(--fold-space-xs);border:0;border-bottom:1px solid var(--fold-color-border-subtle);background:transparent;color:inherit;font:inherit;text-align:start;cursor:pointer}.foldcalw-head:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:2px}.foldcalw-weekday{color:var(--fold-color-text-secondary);font-size:var(--fold-text-xs);font-weight:600;letter-spacing:.06em;text-transform:uppercase}.foldcalw-daynum{color:var(--fold-color-text);font-size:var(--fold-text-lg);font-weight:600;font-variant-numeric:tabular-nums;line-height:1}.is-today .foldcalw-daynum{color:var(--fold-color-primary-text)}.foldcalw-list{display:flex;flex-direction:column;gap:var(--fold-space-xs);margin:0;padding:0;list-style:none}@container (max-width: 150px){.foldcal-chip-sub{display:none}}@container (max-width: 110px){.foldcal-chip-icon{display:none}.foldcal-chip{gap:var(--fold-space-xs);padding-inline-end:var(--fold-space-xs)}}@media(forced-colors:active){.foldcalw-col.is-today{outline:2px solid Highlight;outline-offset:-2px}}\n"] }]
5963
+ }], propDecorators: { date: [{ type: i0.Input, args: [{ isSignal: true, alias: "date", required: true }] }], events: [{ type: i0.Input, args: [{ isSignal: true, alias: "events", required: false }] }], today: [{ type: i0.Input, args: [{ isSignal: true, alias: "today", required: false }] }], weekStartsOn: [{ type: i0.Input, args: [{ isSignal: true, alias: "weekStartsOn", required: false }] }], weekendDays: [{ type: i0.Input, args: [{ isSignal: true, alias: "weekendDays", required: false }] }], dayClick: [{ type: i0.Output, args: ["dayClick"] }], eventClick: [{ type: i0.Output, args: ["eventClick"] }] } });
5964
+
5965
+ /**
5966
+ * `<fold-calendar-day>` — one day, its events listed in full.
5967
+ *
5968
+ * The end of the drill-down: where the month view has room only for a label,
5969
+ * this one has room for the subline too, so it is the view a `dayClick`
5970
+ * naturally lands on.
5971
+ *
5972
+ * ## Inputs
5973
+ * | Input | Type | Default | Meaning |
5974
+ * |----------|--------------------------|---------|---------|
5975
+ * | `date` | `FoldCalendarDate` | — | The day on display. |
5976
+ * | `events` | `FoldCalendarEvent<T>[]` | `[]` | Filtered to those covering `date`. |
5977
+ * | `today` | `FoldCalendarDate` | — | Marks the header when it matches. |
5978
+ * | `locale` | `string` | runtime | Drives the header through `Intl`. |
5979
+ * | `labels` | `Partial<FoldCalendarLabels>` | — | Per-instance label overrides. |
5980
+ *
5981
+ * ## Outputs
5982
+ * | Output | Payload | Fires on |
5983
+ * |--------------|------------------------|----------|
5984
+ * | `eventClick` | `FoldCalendarEvent<T>` | A chip activated. |
5985
+ *
5986
+ * ## Slots
5987
+ * | Selector | Region |
5988
+ * |--------------------------------|--------|
5989
+ * | `button[empty]`, `a[empty]`, `div[empty]` | Under the empty message — the place for a "new request" action. |
5990
+ * | *(default)* | After the list. |
5991
+ *
5992
+ * The `empty` slot is **tag-qualified** (rule 4.8): `empty` is also an input on
5993
+ * `fold-field` and `fold-data-table`, and a bare `[empty]` selector would
5994
+ * capture either of them whole.
5995
+ *
5996
+ * ## Theming
5997
+ * | CSS variable | Default | Sets |
5998
+ * |------------------------------------|---------|------|
5999
+ * | `--fold-calendar-day-number-size` | `--fold-text-3xl` | The big day number. |
6000
+ * | `--fold-calendar-bar-width` | `3px` | The tone bar down a chip's leading edge. |
6001
+ * | `--fold-calendar-band-radius` | `--fold-radius-sm` | Chip corners. |
6002
+ *
6003
+ * @selector `fold-calendar-day`
6004
+ *
6005
+ * @example
6006
+ * ```html
6007
+ * <fold-calendar-day [date]="date()" [events]="events()" [today]="today">
6008
+ * <button empty foldButton (click)="request()">New request</button>
6009
+ * </fold-calendar-day>
6010
+ * ```
6011
+ */
6012
+ class FoldCalendarDayComponent {
6013
+ /** The day on display. */
6014
+ date = input.required(/* @ts-ignore */
6015
+ ...(ngDevMode ? [{ debugName: "date" }] : /* istanbul ignore next */ []));
6016
+ /** Events to filter down to this day. */
6017
+ events = input([], /* @ts-ignore */
6018
+ ...(ngDevMode ? [{ debugName: "events" }] : /* istanbul ignore next */ []));
6019
+ /** The day to mark as today. */
6020
+ today = input(/* @ts-ignore */
6021
+ ...(ngDevMode ? [undefined, { debugName: "today" }] : /* istanbul ignore next */ []));
6022
+ /** A chip was activated. */
6023
+ eventClick = output();
6024
+ /** Labels, locale and the projected chip template — see the directive. */
6025
+ chrome = inject(FoldCalendarChromeDirective);
6026
+ dayEvents = computed(() => foldEventsOnDay(this.events(), this.date()), /* @ts-ignore */
6027
+ ...(ngDevMode ? [{ debugName: "dayEvents" }] : /* istanbul ignore next */ []));
6028
+ isToday = computed(() => this.date() === this.today(), /* @ts-ignore */
6029
+ ...(ngDevMode ? [{ debugName: "isToday" }] : /* istanbul ignore next */ []));
6030
+ /** `Saturday` — the weekday alone; the month sits beside the number. */
6031
+ weekdayName = computed(() => this.chrome.format(this.date(), "weekdayLong"), /* @ts-ignore */
6032
+ ...(ngDevMode ? [{ debugName: "weekdayName" }] : /* istanbul ignore next */ []));
6033
+ /** `May` — read with the day number, which is why it is not in the line above. */
6034
+ monthName = computed(() => this.chrome.format(this.date(), "monthLong"), /* @ts-ignore */
6035
+ ...(ngDevMode ? [{ debugName: "monthName" }] : /* istanbul ignore next */ []));
6036
+ dayOfMonth = computed(() => Number(this.date().slice(8, 10)), /* @ts-ignore */
6037
+ ...(ngDevMode ? [{ debugName: "dayOfMonth" }] : /* istanbul ignore next */ []));
6038
+ /** The header's accessible name: the full date, plus the today marker. */
6039
+ headerLabel = computed(() => {
6040
+ const full = this.chrome.format(this.date(), "dateFull");
6041
+ return this.isToday() ? `${full}, ${this.chrome.l().today}` : full;
6042
+ }, /* @ts-ignore */
6043
+ ...(ngDevMode ? [{ debugName: "headerLabel" }] : /* istanbul ignore next */ []));
6044
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCalendarDayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6045
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldCalendarDayComponent, isStandalone: true, selector: "fold-calendar-day", inputs: { date: { classPropertyName: "date", publicName: "date", isSignal: true, isRequired: true, transformFunction: null }, events: { classPropertyName: "events", publicName: "events", isSignal: true, isRequired: false, transformFunction: null }, today: { classPropertyName: "today", publicName: "today", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { eventClick: "eventClick" }, hostDirectives: [{ directive: FoldCalendarChromeDirective, inputs: ["locale", "locale", "labels", "labels", "formats", "formats"] }], ngImport: i0, template: "<section class=\"foldcald\" [attr.aria-label]=\"headerLabel()\">\n <header class=\"foldcald-head\">\n <span class=\"foldcald-weekday\">{{ weekdayName() }}</span>\n <span\n class=\"foldcald-daynum\"\n [class.is-today]=\"isToday()\"\n [attr.aria-current]=\"isToday() ? 'date' : null\"\n >{{ dayOfMonth() }}</span\n >\n <span class=\"foldcald-month\">{{ monthName() }}</span>\n </header>\n\n @if (dayEvents().length === 0) {\n <div class=\"foldcald-empty\">\n <p>{{ chrome.l().emptyDay }}</p>\n <!--\n Tag-qualified (rule 4.8): `empty` is also an input on fold-field and\n fold-data-table, and a bare [empty] selector would swallow either whole.\n -->\n <ng-content select=\"button[empty], a[empty], div[empty]\" />\n </div>\n } @else {\n <ul class=\"foldcald-list\">\n @for (event of dayEvents(); track event.id) {\n <li>\n <button\n type=\"button\"\n class=\"foldcal-chip foldcald-chip\"\n [attr.data-tone]=\"event.tone ?? 'neutral'\"\n (click)=\"eventClick.emit(event)\"\n >\n @if (chrome.eventContent(); as projected) {\n <ng-container\n [ngTemplateOutlet]=\"projected\"\n [ngTemplateOutletContext]=\"{ $implicit: event, band: null }\"\n />\n } @else {\n <span class=\"foldcal-chip-bar\"></span>\n @if (event.icon; as icon) {\n <fold-icon class=\"foldcal-chip-icon\" [name]=\"icon\" size=\"md\" />\n }\n <span class=\"foldcal-chip-body\">\n <span class=\"foldcal-chip-label\">{{ event.label }}</span>\n @if (event.subline) {\n <span class=\"foldcal-chip-sub\">{{ event.subline }}</span>\n }\n </span>\n }\n </button>\n </li>\n }\n </ul>\n }\n\n <!-- Default slot: anything the qualified selector above did not claim still\n lands somewhere visible instead of disappearing. -->\n <ng-content />\n</section>\n", styles: ["@charset \"UTF-8\";:host{display:block}.foldcal-chip{position:relative;display:flex;align-items:stretch;gap:var(--fold-space-sm);width:100%;padding:var(--fold-space-xs) var(--fold-space-sm);padding-inline-start:0;border:0;border-radius:var(--fold-calendar-band-radius, var(--fold-radius-sm));background:var(--fold-color-surface-raised);color:var(--fold-color-text);font:inherit;text-align:start;cursor:pointer;overflow:hidden;transition:background-color var(--fold-motion-fast) ease}.foldcal-chip:hover{background:var(--fold-color-surface-hover)}.foldcal-chip:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px}.foldcal-chip-bar{flex:0 0 var(--fold-calendar-bar-width, 3px);align-self:stretch;background:var(--fold-color-text-muted)}.foldcal-chip-icon{flex:none;align-self:center;margin-inline-start:var(--fold-space-sm);color:var(--fold-color-text-secondary)}.foldcal-chip-body{display:flex;flex-direction:column;gap:1px;min-width:0}.foldcal-chip-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--fold-text-sm);font-weight:500}.foldcal-chip-sub{color:var(--fold-color-text-secondary);font-size:var(--fold-text-xs)}.foldcal-chip[data-tone=success] .foldcal-chip-bar{background:var(--fold-color-success)}.foldcal-chip[data-tone=warning] .foldcal-chip-bar{background:var(--fold-color-warning)}.foldcal-chip[data-tone=alert] .foldcal-chip-bar{background:var(--fold-color-alert)}.foldcal-chip[data-tone=muted] .foldcal-chip-bar{background:var(--fold-color-text-faded)}.foldcal-chip[data-tone=muted] .foldcal-chip-label{color:var(--fold-color-text-secondary);text-decoration:line-through;text-decoration-thickness:1px}@media(forced-colors:active){.foldcal-chip{border:1px solid CanvasText}.foldcal-chip-bar{forced-color-adjust:none}}@media(prefers-reduced-motion:reduce){.foldcal-chip{transition:none}}.foldcald{display:flex;flex-direction:column;gap:var(--fold-space-lg);padding:var(--fold-space-lg);border:1px solid var(--fold-color-border);border-radius:var(--fold-radius-md);background:var(--fold-color-surface-card)}.foldcald-head{display:flex;align-items:baseline;gap:var(--fold-space-md);padding-bottom:var(--fold-space-md);border-bottom:1px solid var(--fold-color-border)}.foldcald-weekday{color:var(--fold-color-text-secondary);font-size:var(--fold-text-md);font-weight:600;letter-spacing:.04em;text-transform:uppercase}.foldcald-daynum{color:var(--fold-color-text);font-size:var(--fold-calendar-day-number-size, 32px);font-weight:700;font-variant-numeric:tabular-nums;line-height:1}.foldcald-daynum.is-today{color:var(--fold-color-primary-text)}.foldcald-month{color:var(--fold-color-text-secondary);font-size:var(--fold-text-lg);font-weight:600}.foldcald-list{display:flex;flex-direction:column;gap:var(--fold-space-sm);margin:0;padding:0;list-style:none}.foldcald-chip{padding-block:var(--fold-space-sm)}.foldcald-empty{display:flex;flex-direction:column;align-items:flex-start;gap:var(--fold-space-sm);padding:var(--fold-space-lg);border:1px dashed var(--fold-color-border);border-radius:var(--fold-radius-md);background:var(--fold-color-surface-subtle)}.foldcald-empty p{margin:0;color:var(--fold-color-text-secondary);font-size:var(--fold-text-sm)}@media(forced-colors:active){.foldcald-daynum.is-today{outline:2px solid Highlight;outline-offset:2px}.foldcald-head{border-block-end-color:CanvasText}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: FoldIconComponent, selector: "fold-icon", inputs: ["name", "size", "tone", "title"] }] });
6046
+ }
6047
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCalendarDayComponent, decorators: [{
6048
+ type: Component,
6049
+ args: [{ selector: "fold-calendar-day", standalone: true, imports: [NgTemplateOutlet, FoldIconComponent], hostDirectives: [
6050
+ {
6051
+ directive: FoldCalendarChromeDirective,
6052
+ inputs: ["locale", "labels", "formats"],
6053
+ },
6054
+ ], template: "<section class=\"foldcald\" [attr.aria-label]=\"headerLabel()\">\n <header class=\"foldcald-head\">\n <span class=\"foldcald-weekday\">{{ weekdayName() }}</span>\n <span\n class=\"foldcald-daynum\"\n [class.is-today]=\"isToday()\"\n [attr.aria-current]=\"isToday() ? 'date' : null\"\n >{{ dayOfMonth() }}</span\n >\n <span class=\"foldcald-month\">{{ monthName() }}</span>\n </header>\n\n @if (dayEvents().length === 0) {\n <div class=\"foldcald-empty\">\n <p>{{ chrome.l().emptyDay }}</p>\n <!--\n Tag-qualified (rule 4.8): `empty` is also an input on fold-field and\n fold-data-table, and a bare [empty] selector would swallow either whole.\n -->\n <ng-content select=\"button[empty], a[empty], div[empty]\" />\n </div>\n } @else {\n <ul class=\"foldcald-list\">\n @for (event of dayEvents(); track event.id) {\n <li>\n <button\n type=\"button\"\n class=\"foldcal-chip foldcald-chip\"\n [attr.data-tone]=\"event.tone ?? 'neutral'\"\n (click)=\"eventClick.emit(event)\"\n >\n @if (chrome.eventContent(); as projected) {\n <ng-container\n [ngTemplateOutlet]=\"projected\"\n [ngTemplateOutletContext]=\"{ $implicit: event, band: null }\"\n />\n } @else {\n <span class=\"foldcal-chip-bar\"></span>\n @if (event.icon; as icon) {\n <fold-icon class=\"foldcal-chip-icon\" [name]=\"icon\" size=\"md\" />\n }\n <span class=\"foldcal-chip-body\">\n <span class=\"foldcal-chip-label\">{{ event.label }}</span>\n @if (event.subline) {\n <span class=\"foldcal-chip-sub\">{{ event.subline }}</span>\n }\n </span>\n }\n </button>\n </li>\n }\n </ul>\n }\n\n <!-- Default slot: anything the qualified selector above did not claim still\n lands somewhere visible instead of disappearing. -->\n <ng-content />\n</section>\n", styles: ["@charset \"UTF-8\";:host{display:block}.foldcal-chip{position:relative;display:flex;align-items:stretch;gap:var(--fold-space-sm);width:100%;padding:var(--fold-space-xs) var(--fold-space-sm);padding-inline-start:0;border:0;border-radius:var(--fold-calendar-band-radius, var(--fold-radius-sm));background:var(--fold-color-surface-raised);color:var(--fold-color-text);font:inherit;text-align:start;cursor:pointer;overflow:hidden;transition:background-color var(--fold-motion-fast) ease}.foldcal-chip:hover{background:var(--fold-color-surface-hover)}.foldcal-chip:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px}.foldcal-chip-bar{flex:0 0 var(--fold-calendar-bar-width, 3px);align-self:stretch;background:var(--fold-color-text-muted)}.foldcal-chip-icon{flex:none;align-self:center;margin-inline-start:var(--fold-space-sm);color:var(--fold-color-text-secondary)}.foldcal-chip-body{display:flex;flex-direction:column;gap:1px;min-width:0}.foldcal-chip-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--fold-text-sm);font-weight:500}.foldcal-chip-sub{color:var(--fold-color-text-secondary);font-size:var(--fold-text-xs)}.foldcal-chip[data-tone=success] .foldcal-chip-bar{background:var(--fold-color-success)}.foldcal-chip[data-tone=warning] .foldcal-chip-bar{background:var(--fold-color-warning)}.foldcal-chip[data-tone=alert] .foldcal-chip-bar{background:var(--fold-color-alert)}.foldcal-chip[data-tone=muted] .foldcal-chip-bar{background:var(--fold-color-text-faded)}.foldcal-chip[data-tone=muted] .foldcal-chip-label{color:var(--fold-color-text-secondary);text-decoration:line-through;text-decoration-thickness:1px}@media(forced-colors:active){.foldcal-chip{border:1px solid CanvasText}.foldcal-chip-bar{forced-color-adjust:none}}@media(prefers-reduced-motion:reduce){.foldcal-chip{transition:none}}.foldcald{display:flex;flex-direction:column;gap:var(--fold-space-lg);padding:var(--fold-space-lg);border:1px solid var(--fold-color-border);border-radius:var(--fold-radius-md);background:var(--fold-color-surface-card)}.foldcald-head{display:flex;align-items:baseline;gap:var(--fold-space-md);padding-bottom:var(--fold-space-md);border-bottom:1px solid var(--fold-color-border)}.foldcald-weekday{color:var(--fold-color-text-secondary);font-size:var(--fold-text-md);font-weight:600;letter-spacing:.04em;text-transform:uppercase}.foldcald-daynum{color:var(--fold-color-text);font-size:var(--fold-calendar-day-number-size, 32px);font-weight:700;font-variant-numeric:tabular-nums;line-height:1}.foldcald-daynum.is-today{color:var(--fold-color-primary-text)}.foldcald-month{color:var(--fold-color-text-secondary);font-size:var(--fold-text-lg);font-weight:600}.foldcald-list{display:flex;flex-direction:column;gap:var(--fold-space-sm);margin:0;padding:0;list-style:none}.foldcald-chip{padding-block:var(--fold-space-sm)}.foldcald-empty{display:flex;flex-direction:column;align-items:flex-start;gap:var(--fold-space-sm);padding:var(--fold-space-lg);border:1px dashed var(--fold-color-border);border-radius:var(--fold-radius-md);background:var(--fold-color-surface-subtle)}.foldcald-empty p{margin:0;color:var(--fold-color-text-secondary);font-size:var(--fold-text-sm)}@media(forced-colors:active){.foldcald-daynum.is-today{outline:2px solid Highlight;outline-offset:2px}.foldcald-head{border-block-end-color:CanvasText}}\n"] }]
6055
+ }], propDecorators: { date: [{ type: i0.Input, args: [{ isSignal: true, alias: "date", required: true }] }], events: [{ type: i0.Input, args: [{ isSignal: true, alias: "events", required: false }] }], today: [{ type: i0.Input, args: [{ isSignal: true, alias: "today", required: false }] }], eventClick: [{ type: i0.Output, args: ["eventClick"] }] } });
6056
+
6057
+ /**
6058
+ * `<fold-calendar-list>` — everything in a range, in date order.
6059
+ *
6060
+ * The flat reading of the same data: no grid, no packing, so nothing is ever
6061
+ * hidden behind a lane budget. Each row leads with the span it covers, which
6062
+ * is what the grid views can only imply.
6063
+ *
6064
+ * For a sortable, column-configurable table instead, feed
6065
+ * `foldEventsInRange()` straight into {@link FoldDataTableComponent} — this
6066
+ * view stays deliberately small.
6067
+ *
6068
+ * ## Inputs
6069
+ * | Input | Type | Default | Meaning |
6070
+ * |----------|--------------------------|---------|---------|
6071
+ * | `events` | `FoldCalendarEvent<T>[]` | `[]` | Source events. |
6072
+ * | `from` | `FoldCalendarDate` | — | Range start; omit to list everything. |
6073
+ * | `to` | `FoldCalendarDate` | — | Range end; omit to list everything. |
6074
+ * | `locale` | `string` | runtime | Drives the span labels through `Intl`. |
6075
+ * | `labels` | `Partial<FoldCalendarLabels>` | — | Per-instance label overrides. |
6076
+ *
6077
+ * ## Outputs
6078
+ * | Output | Payload | Fires on |
6079
+ * |--------------|------------------------|----------|
6080
+ * | `eventClick` | `FoldCalendarEvent<T>` | A row activated. |
6081
+ *
6082
+ * ## Theming
6083
+ * | CSS variable | Default | Sets |
6084
+ * |------------------------------------|---------|------|
6085
+ * | `--fold-calendar-list-date-width` | `7rem` | The leading date column — widen it for verbose locales. |
6086
+ * | `--fold-calendar-bar-width` | `3px` | The tone bar down a row's leading edge. |
6087
+ *
6088
+ * A projected `foldCalendarEvent` template replaces a row's **whole** inside,
6089
+ * as in every other view: the built-in bar and date belong to the default
6090
+ * rendering, not to the frame around it.
6091
+ *
6092
+ * @selector `fold-calendar-list`
6093
+ *
6094
+ * @example
6095
+ * ```html
6096
+ * <fold-calendar-list
6097
+ * [events]="events()"
6098
+ * [from]="range().from"
6099
+ * [to]="range().to"
6100
+ * (eventClick)="open($event)"
6101
+ * />
6102
+ * ```
6103
+ */
6104
+ class FoldCalendarListComponent {
6105
+ /** Source events. */
6106
+ events = input([], /* @ts-ignore */
6107
+ ...(ngDevMode ? [{ debugName: "events" }] : /* istanbul ignore next */ []));
6108
+ /** Range start, inclusive. Omit (with `to`) to list every event given. */
6109
+ from = input(/* @ts-ignore */
6110
+ ...(ngDevMode ? [undefined, { debugName: "from" }] : /* istanbul ignore next */ []));
6111
+ /** Range end, inclusive. */
6112
+ to = input(/* @ts-ignore */
6113
+ ...(ngDevMode ? [undefined, { debugName: "to" }] : /* istanbul ignore next */ []));
6114
+ /** A row was activated. */
6115
+ eventClick = output();
6116
+ /** Labels, locale and the projected chip template — see the directive. */
6117
+ chrome = inject(FoldCalendarChromeDirective);
6118
+ /** In range (when one is given), earliest first. */
6119
+ rows = computed(() => {
6120
+ const from = this.from();
6121
+ const to = this.to();
6122
+ const scoped = from === undefined || to === undefined
6123
+ ? this.events()
6124
+ : foldEventsInRange(this.events(), from, to);
6125
+ return [...scoped].sort((a, b) => a.start < b.start ? -1 : a.start > b.start ? 1 : 0);
6126
+ }, /* @ts-ignore */
6127
+ ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
6128
+ /**
6129
+ * The span, collapsed to one date when it covers a single day — `Intl`'s
6130
+ * own range formatter does that, and picks the locale's separator and its
6131
+ * order, which is why this is not a translatable label.
6132
+ */
6133
+ rangeLabel(event) {
6134
+ return this.chrome.formatRange(event.start, event.end, "dayMonthShort");
6135
+ }
6136
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCalendarListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6137
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldCalendarListComponent, isStandalone: true, selector: "fold-calendar-list", inputs: { events: { classPropertyName: "events", publicName: "events", isSignal: true, isRequired: false, transformFunction: null }, from: { classPropertyName: "from", publicName: "from", isSignal: true, isRequired: false, transformFunction: null }, to: { classPropertyName: "to", publicName: "to", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { eventClick: "eventClick" }, hostDirectives: [{ directive: FoldCalendarChromeDirective, inputs: ["locale", "locale", "labels", "labels", "formats", "formats"] }], ngImport: i0, template: "@if (rows().length === 0) {\n <p class=\"foldcall-empty\">{{ chrome.l().emptyRange }}</p>\n} @else {\n <ul class=\"foldcall\">\n @for (event of rows(); track event.id) {\n <li>\n <!--\n A projected template replaces the row's **whole** inside, exactly as\n it does in the other four views. Rendering the bar and the date\n outside the @if would make the same template mean something different\n here, and drop a one-element template into a four-column grid.\n -->\n <button\n type=\"button\"\n class=\"foldcall-row\"\n [class.is-custom]=\"chrome.eventContent() !== null\"\n [attr.data-tone]=\"event.tone ?? 'neutral'\"\n (click)=\"eventClick.emit(event)\"\n >\n @if (chrome.eventContent(); as projected) {\n <ng-container\n [ngTemplateOutlet]=\"projected\"\n [ngTemplateOutletContext]=\"{ $implicit: event, band: null }\"\n />\n } @else {\n <span class=\"foldcall-bar\"></span>\n <span class=\"foldcall-when\">{{ rangeLabel(event) }}</span>\n <span class=\"foldcall-body\">\n @if (event.icon; as icon) {\n <fold-icon class=\"foldcall-icon\" [name]=\"icon\" size=\"sm\" />\n }\n <span class=\"foldcall-label\">{{ event.label }}</span>\n </span>\n <span class=\"foldcall-sub\">{{ event.subline ?? \"\" }}</span>\n }\n </button>\n </li>\n }\n </ul>\n}\n", styles: [":host{display:block;container-type:inline-size}.foldcall{display:flex;flex-direction:column;margin:0;padding:0;list-style:none;border:1px solid var(--fold-color-border);border-radius:var(--fold-radius-md);background:var(--fold-color-surface-card);overflow:clip}.foldcall li+li .foldcall-row{border-top:1px solid var(--fold-color-border-subtle)}.foldcall-row{display:grid;grid-template-columns:var(--fold-calendar-bar-width, 3px) var(--fold-calendar-list-date-width, 7rem) minmax(0,1fr) auto;align-items:center;gap:var(--fold-space-md);width:100%;padding:var(--fold-space-sm) var(--fold-space-md);padding-inline-start:0;border:0;background:transparent;color:inherit;font:inherit;text-align:start;cursor:pointer;transition:background-color var(--fold-motion-fast) ease}.foldcall-row.is-custom{display:flex;align-items:center;gap:var(--fold-space-sm);padding-inline-start:var(--fold-space-md)}.foldcall-row:hover{background:var(--fold-color-surface-hover)}.foldcall-row:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px}.foldcall-bar{align-self:stretch;background:var(--fold-color-text-muted)}.foldcall-when{color:var(--fold-color-text-secondary);font-size:var(--fold-text-sm);font-variant-numeric:tabular-nums;white-space:nowrap}.foldcall-body{display:flex;align-items:center;gap:var(--fold-space-sm);min-width:0}.foldcall-icon{flex:none;color:var(--fold-color-text-secondary)}.foldcall-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--fold-text-sm);font-weight:500}.foldcall-sub{color:var(--fold-color-text-secondary);font-size:var(--fold-text-xs);white-space:nowrap}.foldcall-empty{margin:0;padding:var(--fold-space-xl);border:1px dashed var(--fold-color-border);border-radius:var(--fold-radius-md);background:var(--fold-color-surface-subtle);color:var(--fold-color-text-secondary);font-size:var(--fold-text-sm);text-align:center}.foldcall-row[data-tone=success] .foldcall-bar{background:var(--fold-color-success)}.foldcall-row[data-tone=warning] .foldcall-bar{background:var(--fold-color-warning)}.foldcall-row[data-tone=alert] .foldcall-bar{background:var(--fold-color-alert)}.foldcall-row[data-tone=muted] .foldcall-bar{background:var(--fold-color-text-faded)}.foldcall-row[data-tone=muted] .foldcall-label{color:var(--fold-color-text-secondary);text-decoration:line-through;text-decoration-thickness:1px}@media(forced-colors:active){.foldcall-row{border:1px solid CanvasText}.foldcall-bar{forced-color-adjust:none}}@container (max-width: 640px){.foldcall-row:not(.is-custom){grid-template-columns:var(--fold-calendar-bar-width, 3px) var(--fold-calendar-list-date-width, 7rem) minmax(0,1fr)}.foldcall-sub{grid-column:2/-1}}@media(forced-colors:active){.foldcall-row:focus-visible{outline-color:Highlight}}@media(prefers-reduced-motion:reduce){.foldcall-row{transition:none}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: FoldIconComponent, selector: "fold-icon", inputs: ["name", "size", "tone", "title"] }] });
6138
+ }
6139
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCalendarListComponent, decorators: [{
6140
+ type: Component,
6141
+ args: [{ selector: "fold-calendar-list", standalone: true, imports: [NgTemplateOutlet, FoldIconComponent], hostDirectives: [
6142
+ {
6143
+ directive: FoldCalendarChromeDirective,
6144
+ inputs: ["locale", "labels", "formats"],
6145
+ },
6146
+ ], template: "@if (rows().length === 0) {\n <p class=\"foldcall-empty\">{{ chrome.l().emptyRange }}</p>\n} @else {\n <ul class=\"foldcall\">\n @for (event of rows(); track event.id) {\n <li>\n <!--\n A projected template replaces the row's **whole** inside, exactly as\n it does in the other four views. Rendering the bar and the date\n outside the @if would make the same template mean something different\n here, and drop a one-element template into a four-column grid.\n -->\n <button\n type=\"button\"\n class=\"foldcall-row\"\n [class.is-custom]=\"chrome.eventContent() !== null\"\n [attr.data-tone]=\"event.tone ?? 'neutral'\"\n (click)=\"eventClick.emit(event)\"\n >\n @if (chrome.eventContent(); as projected) {\n <ng-container\n [ngTemplateOutlet]=\"projected\"\n [ngTemplateOutletContext]=\"{ $implicit: event, band: null }\"\n />\n } @else {\n <span class=\"foldcall-bar\"></span>\n <span class=\"foldcall-when\">{{ rangeLabel(event) }}</span>\n <span class=\"foldcall-body\">\n @if (event.icon; as icon) {\n <fold-icon class=\"foldcall-icon\" [name]=\"icon\" size=\"sm\" />\n }\n <span class=\"foldcall-label\">{{ event.label }}</span>\n </span>\n <span class=\"foldcall-sub\">{{ event.subline ?? \"\" }}</span>\n }\n </button>\n </li>\n }\n </ul>\n}\n", styles: [":host{display:block;container-type:inline-size}.foldcall{display:flex;flex-direction:column;margin:0;padding:0;list-style:none;border:1px solid var(--fold-color-border);border-radius:var(--fold-radius-md);background:var(--fold-color-surface-card);overflow:clip}.foldcall li+li .foldcall-row{border-top:1px solid var(--fold-color-border-subtle)}.foldcall-row{display:grid;grid-template-columns:var(--fold-calendar-bar-width, 3px) var(--fold-calendar-list-date-width, 7rem) minmax(0,1fr) auto;align-items:center;gap:var(--fold-space-md);width:100%;padding:var(--fold-space-sm) var(--fold-space-md);padding-inline-start:0;border:0;background:transparent;color:inherit;font:inherit;text-align:start;cursor:pointer;transition:background-color var(--fold-motion-fast) ease}.foldcall-row.is-custom{display:flex;align-items:center;gap:var(--fold-space-sm);padding-inline-start:var(--fold-space-md)}.foldcall-row:hover{background:var(--fold-color-surface-hover)}.foldcall-row:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px}.foldcall-bar{align-self:stretch;background:var(--fold-color-text-muted)}.foldcall-when{color:var(--fold-color-text-secondary);font-size:var(--fold-text-sm);font-variant-numeric:tabular-nums;white-space:nowrap}.foldcall-body{display:flex;align-items:center;gap:var(--fold-space-sm);min-width:0}.foldcall-icon{flex:none;color:var(--fold-color-text-secondary)}.foldcall-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--fold-text-sm);font-weight:500}.foldcall-sub{color:var(--fold-color-text-secondary);font-size:var(--fold-text-xs);white-space:nowrap}.foldcall-empty{margin:0;padding:var(--fold-space-xl);border:1px dashed var(--fold-color-border);border-radius:var(--fold-radius-md);background:var(--fold-color-surface-subtle);color:var(--fold-color-text-secondary);font-size:var(--fold-text-sm);text-align:center}.foldcall-row[data-tone=success] .foldcall-bar{background:var(--fold-color-success)}.foldcall-row[data-tone=warning] .foldcall-bar{background:var(--fold-color-warning)}.foldcall-row[data-tone=alert] .foldcall-bar{background:var(--fold-color-alert)}.foldcall-row[data-tone=muted] .foldcall-bar{background:var(--fold-color-text-faded)}.foldcall-row[data-tone=muted] .foldcall-label{color:var(--fold-color-text-secondary);text-decoration:line-through;text-decoration-thickness:1px}@media(forced-colors:active){.foldcall-row{border:1px solid CanvasText}.foldcall-bar{forced-color-adjust:none}}@container (max-width: 640px){.foldcall-row:not(.is-custom){grid-template-columns:var(--fold-calendar-bar-width, 3px) var(--fold-calendar-list-date-width, 7rem) minmax(0,1fr)}.foldcall-sub{grid-column:2/-1}}@media(forced-colors:active){.foldcall-row:focus-visible{outline-color:Highlight}}@media(prefers-reduced-motion:reduce){.foldcall-row{transition:none}}\n"] }]
6147
+ }], propDecorators: { events: [{ type: i0.Input, args: [{ isSignal: true, alias: "events", required: false }] }], from: [{ type: i0.Input, args: [{ isSignal: true, alias: "from", required: false }] }], to: [{ type: i0.Input, args: [{ isSignal: true, alias: "to", required: false }] }], eventClick: [{ type: i0.Output, args: ["eventClick"] }] } });
6148
+
6149
+ /**
6150
+ * Where overlapping timed events sit side by side — the time grid's only
6151
+ * genuinely algorithmic part, and the same shape as the month's lane packing
6152
+ * turned ninety degrees: intervals over **minutes** instead of days, laid out
6153
+ * across the column's width instead of down its height.
6154
+ *
6155
+ * Pure, so the rule can be exercised exhaustively without a grid.
6156
+ */
6157
+ /**
6158
+ * The narrowest an event is allowed to get. Past this, three overlapping
6159
+ * meetings are three unreadable slivers, and a reader is better served by a
6160
+ * `+N` than by a column two pixels wide — which is the caller's call to make,
6161
+ * so this only reports the number.
6162
+ */
6163
+ const MIN_VISIBLE_COLUMNS = 1;
6164
+ /** Whether two intervals actually share a minute. */
6165
+ function overlaps(a, b) {
6166
+ return a.startMinute < b.endMinute && b.startMinute < a.endMinute;
6167
+ }
6168
+ /**
6169
+ * Lays intervals out across the width, one slot per input, in input order.
6170
+ *
6171
+ * Two events that merely touch — one ends at 10:00, the next starts at 10:00 —
6172
+ * do **not** overlap and each keep the full width. That is the difference
6173
+ * between the month grid (inclusive days: touching means colliding) and a time
6174
+ * grid (exclusive minutes: touching means back-to-back), and getting it wrong
6175
+ * halves the width of every event in a packed morning.
6176
+ *
6177
+ * Events are grouped into **clusters** — runs connected by overlap — and each
6178
+ * cluster is widened only as far as it needs, so one triple-booked hour does
6179
+ * not narrow the rest of the day.
6180
+ */
6181
+ function foldLayOutOverlaps(intervals) {
6182
+ const order = intervals
6183
+ .map((interval, index) => ({ interval, index }))
6184
+ .sort((a, b) => a.interval.startMinute !== b.interval.startMinute
6185
+ ? a.interval.startMinute - b.interval.startMinute
6186
+ : b.interval.endMinute -
6187
+ b.interval.startMinute -
6188
+ (a.interval.endMinute - a.interval.startMinute));
6189
+ const slots = intervals.map(() => ({
6190
+ column: 0,
6191
+ columns: MIN_VISIBLE_COLUMNS,
6192
+ }));
6193
+ /** The cluster being filled: its members' indices and its lane end times. */
6194
+ let cluster = [];
6195
+ let laneEnds = [];
6196
+ const closeCluster = () => {
6197
+ for (const index of cluster) {
6198
+ slots[index] = {
6199
+ column: slots[index]?.column ?? 0,
6200
+ columns: laneEnds.length,
6201
+ };
6202
+ }
6203
+ cluster = [];
6204
+ laneEnds = [];
6205
+ };
6206
+ for (const { interval, index } of order) {
6207
+ // A cluster ends where nothing it holds is still running.
6208
+ const stillRunning = laneEnds.some((end) => end > interval.startMinute);
6209
+ if (!stillRunning && cluster.length > 0) {
6210
+ closeCluster();
6211
+ }
6212
+ const reusable = laneEnds.findIndex((end) => end <= interval.startMinute);
6213
+ const column = reusable === -1 ? laneEnds.length : reusable;
6214
+ laneEnds[column] = interval.endMinute;
6215
+ slots[index] = { column, columns: laneEnds.length };
6216
+ cluster.push(index);
6217
+ }
6218
+ if (cluster.length > 0) {
6219
+ closeCluster();
6220
+ }
6221
+ return slots;
6222
+ }
6223
+ /** Whether `a` and `b` would need separate columns — exported for tests. */
6224
+ function foldOverlaps(a, b) {
6225
+ return overlaps(a, b);
6226
+ }
6227
+
6228
+ const DEFAULT_DAY_COUNT = 7;
6229
+ const DEFAULT_ALL_DAY_LANES = 2;
6230
+ const MINUTES_PER_HOUR = 60;
6231
+ /**
6232
+ * Whether an event is placed on the clock or on the strip.
6233
+ *
6234
+ * **Both** ends are needed: an event with only a start has no height, and one
6235
+ * with only an end has no origin. Half a time is a data bug, and treating it as
6236
+ * all-day is the reading that loses the least.
6237
+ */
6238
+ function foldIsTimed(event) {
6239
+ return event.startTime !== undefined && event.endTime !== undefined;
6240
+ }
6241
+ /** The window a timed event occupies on one particular day, or `null`. */
6242
+ function minutesOnDay(event, day, startMinute, endMinute) {
6243
+ const firstDay = foldToEpochDay(event.start);
6244
+ const lastDay = foldToEpochDay(event.end);
6245
+ if (day < firstDay || day > lastDay) {
6246
+ return null;
6247
+ }
6248
+ // A span crossing midnight runs to the end of each day it passes through:
6249
+ // Monday 14:00 → Wednesday 10:00 is three blocks, not one impossible one.
6250
+ const rawFrom = day === firstDay ? foldToMinutes(event.startTime ?? "00:00") : 0;
6251
+ const rawTo = day === lastDay
6252
+ ? foldToMinutes(event.endTime ?? "24:00")
6253
+ : FOLD_MINUTES_PER_DAY;
6254
+ const from = Math.max(startMinute, Math.min(rawFrom, rawTo));
6255
+ const to = Math.min(endMinute, Math.max(rawFrom, rawTo));
6256
+ if (to <= from) {
6257
+ return null; // entirely outside the visible window
6258
+ }
6259
+ return {
6260
+ from,
6261
+ to,
6262
+ before: day > firstDay || rawFrom < startMinute,
6263
+ after: day < lastDay || rawTo > endMinute,
6264
+ };
6265
+ }
6266
+ /** The grid's own window: which days, and which minutes of them. */
6267
+ function bounds(options) {
6268
+ const dayCount = Math.max(1, Math.trunc(options.dayCount ?? DEFAULT_DAY_COUNT));
6269
+ const snap = options.snapToWeek !== false && dayCount === DEFAULT_DAY_COUNT;
6270
+ const firstDay = foldToEpochDay(snap
6271
+ ? foldStartOfWeek(options.date, options.weekStartsOn ?? "mon")
6272
+ : options.date);
6273
+ const startMinute = foldToMinutes(options.dayStart ?? "00:00");
6274
+ const rawEnd = foldToMinutes(options.dayEnd ?? "24:00");
6275
+ return {
6276
+ firstDay,
6277
+ dayCount,
6278
+ startMinute,
6279
+ // A window with no height would divide by zero downstream.
6280
+ endMinute: rawEnd > startMinute ? rawEnd : FOLD_MINUTES_PER_DAY,
6281
+ };
6282
+ }
6283
+ /** The hour marks inside the window, including the one it opens on. */
6284
+ function hourMarks(startMinute, endMinute) {
6285
+ const marks = [];
6286
+ const first = Math.ceil(startMinute / MINUTES_PER_HOUR) * MINUTES_PER_HOUR;
6287
+ for (let minute = first; minute < endMinute; minute += MINUTES_PER_HOUR) {
6288
+ const hour = Math.floor(minute / MINUTES_PER_HOUR);
6289
+ marks.push(`${hour < 10 ? "0" : ""}${hour}:00`);
6290
+ }
6291
+ return marks;
6292
+ }
6293
+ /**
6294
+ * Lays out `dayCount` days as hour columns plus an all-day strip.
6295
+ *
6296
+ * Timed events are placed on the clock; everything else — no times, or only one
6297
+ * of them — goes on the strip, where it spans days exactly as it does in the
6298
+ * month grid.
6299
+ */
6300
+ function foldBuildTimeGrid(events, options) {
6301
+ const { firstDay, dayCount, startMinute, endMinute } = bounds(options);
6302
+ const span = endMinute - startMinute;
6303
+ const timed = events.filter((event) => foldIsTimed(event));
6304
+ const allDayEvents = events.filter((event) => !foldIsTimed(event));
6305
+ const hidden = new Map();
6306
+ const strip = foldPackRow(foldCollapseGroups(allDayEvents).filter((candidate) => candidate.endDay >= firstDay &&
6307
+ candidate.startDay < firstDay + dayCount), {
6308
+ start: foldFromEpochDay(firstDay),
6309
+ startDay: firstDay,
6310
+ dayCount,
6311
+ }, Math.max(0, Math.trunc(options.maxAllDayLanes ?? DEFAULT_ALL_DAY_LANES)), hidden);
6312
+ const context = {
6313
+ month: foldFromEpochDay(firstDay).slice(0, 7),
6314
+ today: options.today,
6315
+ weekendDays: foldWeekendOr(options.weekendDays),
6316
+ counts: foldCountByDay(events, firstDay, firstDay + dayCount - 1),
6317
+ hidden: FOLD_NO_HIDDEN_DAYS,
6318
+ };
6319
+ const columns = Array.from({ length: dayCount }, (_unused, offset) => {
6320
+ const day = firstDay + offset;
6321
+ const date = foldFromEpochDay(day);
6322
+ const placed = timed
6323
+ .map((event) => ({
6324
+ event,
6325
+ window: minutesOnDay(event, day, startMinute, endMinute),
6326
+ }))
6327
+ .filter((candidate) => candidate.window !== null);
6328
+ const slots = foldLayOutOverlaps(placed.map(({ window }) => ({
6329
+ startMinute: window.from,
6330
+ endMinute: window.to,
6331
+ })));
6332
+ return {
6333
+ day: foldMakeDay(day, context),
6334
+ blocks: placed.map(({ event, window }, index) => ({
6335
+ key: `${event.id}@${date}`,
6336
+ event,
6337
+ top: (window.from - startMinute) / span,
6338
+ height: (window.to - window.from) / span,
6339
+ column: slots[index]?.column ?? 0,
6340
+ columns: slots[index]?.columns ?? 1,
6341
+ continuesBefore: window.before,
6342
+ continuesAfter: window.after,
6343
+ })),
6344
+ };
6345
+ });
6346
+ return {
6347
+ columns,
6348
+ allDay: strip.bands,
6349
+ allDayHiddenCount: strip.hiddenCount,
6350
+ hours: hourMarks(startMinute, endMinute),
6351
+ startMinute,
6352
+ endMinute,
6353
+ };
6354
+ }
6355
+
6356
+ /**
6357
+ * `<fold-calendar-timegrid>` — days as hour columns, with an all-day strip.
6358
+ *
6359
+ * The reading the other four cannot give: **when inside a day**. A meeting is a
6360
+ * block whose height is its duration and whose width is shared with whatever it
6361
+ * collides with; an absence is a band across the strip on top, spanning days
6362
+ * exactly as it does in the month view — the same packer draws both, so a
6363
+ * three-day leave request reads identically wherever it appears.
6364
+ *
6365
+ * Times are wall-clock `HH:mm`, never instants — the same decision as the date,
6366
+ * for the same reason. 09:00 is the hour on the wall; the app converts once at
6367
+ * its own boundary, and no zone can move a meeting after that.
6368
+ *
6369
+ * ## Inputs
6370
+ * | Input | Type | Default | Meaning |
6371
+ * |-------------------|--------------------------|-----------|---------|
6372
+ * | `date` | `FoldCalendarDate` | — | Any date in the range. |
6373
+ * | `events` | `FoldCalendarEvent<T>[]` | `[]` | Timed events go on the clock, the rest on the strip. |
6374
+ * | `dayCount` | `number` | `7` | `1` is a day view, `7` a week. |
6375
+ * | `today` | `FoldCalendarDate` | — | The column to mark. |
6376
+ * | `now` | `FoldCalendarTime` | — | Draws the current-time line on `today`'s column. Passed in, never read from a clock — the package has none, and an SSR render must not invent one. |
6377
+ * | `dayStart`/`dayEnd` | `FoldCalendarTime` | `00:00`/`24:00` | The visible window; `08:00`–`20:00` is a working day. |
6378
+ * | `weekStartsOn` | `FoldWeekday` | the locale's | Which day opens the week. |
6379
+ * | `weekendDays` | `FoldWeekday[]` | the locale's | Which columns are shaded. |
6380
+ * | `maxAllDayLanes` | `number` | `2` | Lanes the strip stacks before overflowing. |
6381
+ * | `locale`/`labels`/`formats` | — | — | See the other views. |
6382
+ *
6383
+ * ## Outputs
6384
+ * | Output | Payload | Fires on |
6385
+ * |--------------|------------------------|----------|
6386
+ * | `dayClick` | `FoldCalendarDate` | A column header activated. |
6387
+ * | `eventClick` | `FoldCalendarEvent<T>` | A block or an all-day band activated. |
6388
+ *
6389
+ * ## Theming
6390
+ * | CSS variable | Default | Sets |
6391
+ * |---------------------------------------|---------|------|
6392
+ * | `--fold-calendar-timegrid-height` | `640px` | Height of the hour area. |
6393
+ * | `--fold-calendar-timegrid-gutter` | `56px` | Width of the hour labels. |
6394
+ * | `--fold-calendar-timegrid-lane` | `20px` | Height of one all-day lane. |
6395
+ * | `--fold-calendar-bar-width` | `3px` | The tone bar down a block's leading edge. |
6396
+ *
6397
+ * ## Accessibility
6398
+ * Every block and every band is a real `<button>` inside the column it belongs
6399
+ * to, so the tab order follows the day and this view needs none of the month
6400
+ * grid's roving-tabindex machinery. A block's accessible name carries its
6401
+ * **times**, which its position on the clock conveys only visually.
6402
+ *
6403
+ * @selector `fold-calendar-timegrid`
6404
+ *
6405
+ * @example
6406
+ * ```html
6407
+ * <fold-calendar-timegrid
6408
+ * [date]="date()"
6409
+ * [events]="events()"
6410
+ * [today]="today"
6411
+ * [now]="now()"
6412
+ * dayStart="08:00"
6413
+ * dayEnd="20:00"
6414
+ * (eventClick)="open($event)"
6415
+ * />
6416
+ * ```
6417
+ */
6418
+ class FoldCalendarTimegridComponent {
6419
+ /** Any date inside the range on display. */
6420
+ date = input.required(/* @ts-ignore */
6421
+ ...(ngDevMode ? [{ debugName: "date" }] : /* istanbul ignore next */ []));
6422
+ /** Events to plot; timed ones on the clock, the rest on the strip. */
6423
+ events = input([], /* @ts-ignore */
6424
+ ...(ngDevMode ? [{ debugName: "events" }] : /* istanbul ignore next */ []));
6425
+ /** Days to show: `1` is a day view, `7` a week. @default 7 */
6426
+ dayCount = input(7, { ...(ngDevMode ? { debugName: "dayCount" } : /* istanbul ignore next */ {}), transform: numberAttribute });
6427
+ /** The day to mark as today. */
6428
+ today = input(/* @ts-ignore */
6429
+ ...(ngDevMode ? [undefined, { debugName: "today" }] : /* istanbul ignore next */ []));
6430
+ /**
6431
+ * The current wall-clock time, for the "now" line. Passed in rather than
6432
+ * read: this package owns no clock (`today` works the same way), and a
6433
+ * server render that invented one would hydrate to a different position.
6434
+ */
6435
+ now = input(/* @ts-ignore */
6436
+ ...(ngDevMode ? [undefined, { debugName: "now" }] : /* istanbul ignore next */ []));
6437
+ /** First hour on screen. @default '00:00' */
6438
+ dayStart = input("00:00", /* @ts-ignore */
6439
+ ...(ngDevMode ? [{ debugName: "dayStart" }] : /* istanbul ignore next */ []));
6440
+ /** Last hour on screen. @default '24:00' */
6441
+ dayEnd = input("24:00", /* @ts-ignore */
6442
+ ...(ngDevMode ? [{ debugName: "dayEnd" }] : /* istanbul ignore next */ []));
6443
+ /** Day the week starts on. @default the locale's own first day */
6444
+ weekStartsOn = input(/* @ts-ignore */
6445
+ ...(ngDevMode ? [undefined, { debugName: "weekStartsOn" }] : /* istanbul ignore next */ []));
6446
+ /** Days shaded as the weekend. @default the locale's own weekend */
6447
+ weekendDays = input(/* @ts-ignore */
6448
+ ...(ngDevMode ? [undefined, { debugName: "weekendDays" }] : /* istanbul ignore next */ []));
6449
+ /** Lanes the all-day strip stacks before events overflow. @default 2 */
6450
+ maxAllDayLanes = input(2, { ...(ngDevMode ? { debugName: "maxAllDayLanes" } : /* istanbul ignore next */ {}), transform: numberAttribute });
6451
+ /** A column header was activated. */
6452
+ dayClick = output();
6453
+ /** A block or an all-day band was activated. */
6454
+ eventClick = output();
6455
+ /** Labels, locale and the projected chip template — see the directive. */
6456
+ chrome = inject(FoldCalendarChromeDirective);
6457
+ grid = computed(() => foldBuildTimeGrid(this.events(), {
6458
+ date: this.date(),
6459
+ dayCount: this.dayCount(),
6460
+ weekStartsOn: this.chrome.anchor(this.weekStartsOn()),
6461
+ weekendDays: this.chrome.weekend(this.weekendDays()),
6462
+ today: this.today(),
6463
+ dayStart: this.dayStart(),
6464
+ dayEnd: this.dayEnd(),
6465
+ maxAllDayLanes: this.maxAllDayLanes(),
6466
+ }), /* @ts-ignore */
6467
+ ...(ngDevMode ? [{ debugName: "grid" }] : /* istanbul ignore next */ []));
6468
+ /** Lane rows for the all-day strip, or none when nothing spans. */
6469
+ stripRows = computed(() => {
6470
+ const lanes = this.grid().allDay.reduce((highest, band) => Math.max(highest, band.lane + 1), 0);
6471
+ return `repeat(${lanes}, var(--fold-calendar-timegrid-lane, 20px))`;
6472
+ }, /* @ts-ignore */
6473
+ ...(ngDevMode ? [{ debugName: "stripRows" }] : /* istanbul ignore next */ []));
6474
+ /** Where the "now" line sits, as a fraction of the window, or `null`. */
6475
+ nowOffset = computed(() => {
6476
+ const time = this.now();
6477
+ if (time === undefined) {
6478
+ return null;
6479
+ }
6480
+ const { startMinute, endMinute } = this.grid();
6481
+ const minute = foldToMinutes(time);
6482
+ if (minute < startMinute || minute > endMinute) {
6483
+ return null;
6484
+ }
6485
+ return (minute - startMinute) / (endMinute - startMinute);
6486
+ }, /* @ts-ignore */
6487
+ ...(ngDevMode ? [{ debugName: "nowOffset" }] : /* istanbul ignore next */ []));
6488
+ weekdayName(column) {
6489
+ return this.chrome.format(column.day.date, "weekdayShort");
6490
+ }
6491
+ hourLabel(hour) {
6492
+ return this.chrome.formatTime(hour);
6493
+ }
6494
+ /** A column's accessible name: its date, the today marker, and its load. */
6495
+ columnLabel(column) {
6496
+ const labels = this.chrome.l();
6497
+ const parts = [this.chrome.format(column.day.date, "dateFull")];
6498
+ if (column.day.isToday) {
6499
+ parts.push(labels.today);
6500
+ }
6501
+ if (column.day.eventCount > 0) {
6502
+ parts.push(labels.eventCount(column.day.eventCount));
6503
+ }
6504
+ return parts.join(", ");
6505
+ }
6506
+ /**
6507
+ * A block's accessible name — its times first, because where it sits on the
6508
+ * clock is information a screen reader has no other way to get.
6509
+ */
6510
+ blockLabel(block) {
6511
+ const { startTime, endTime, label } = block.event;
6512
+ if (startTime === undefined || endTime === undefined) {
6513
+ return label;
6514
+ }
6515
+ return `${this.chrome.formatTime(startTime)} – ${this.chrome.formatTime(endTime)}, ${label}`;
6516
+ }
6517
+ /** A band's text: the group's label when it stands for several events. */
6518
+ bandLabel(band) {
6519
+ if (band.groupSize > 1) {
6520
+ return band.event.groupLabel ?? band.event.label;
6521
+ }
6522
+ return band.event.label;
6523
+ }
6524
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCalendarTimegridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6525
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldCalendarTimegridComponent, isStandalone: true, selector: "fold-calendar-timegrid", inputs: { date: { classPropertyName: "date", publicName: "date", isSignal: true, isRequired: true, transformFunction: null }, events: { classPropertyName: "events", publicName: "events", isSignal: true, isRequired: false, transformFunction: null }, dayCount: { classPropertyName: "dayCount", publicName: "dayCount", isSignal: true, isRequired: false, transformFunction: null }, today: { classPropertyName: "today", publicName: "today", isSignal: true, isRequired: false, transformFunction: null }, now: { classPropertyName: "now", publicName: "now", isSignal: true, isRequired: false, transformFunction: null }, dayStart: { classPropertyName: "dayStart", publicName: "dayStart", isSignal: true, isRequired: false, transformFunction: null }, dayEnd: { classPropertyName: "dayEnd", publicName: "dayEnd", isSignal: true, isRequired: false, transformFunction: null }, weekStartsOn: { classPropertyName: "weekStartsOn", publicName: "weekStartsOn", isSignal: true, isRequired: false, transformFunction: null }, weekendDays: { classPropertyName: "weekendDays", publicName: "weekendDays", isSignal: true, isRequired: false, transformFunction: null }, maxAllDayLanes: { classPropertyName: "maxAllDayLanes", publicName: "maxAllDayLanes", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dayClick: "dayClick", eventClick: "eventClick" }, hostDirectives: [{ directive: FoldCalendarChromeDirective, inputs: ["locale", "locale", "labels", "labels", "formats", "formats"] }], ngImport: i0, template: "<div class=\"foldcaltg\" [attr.aria-label]=\"chrome.l().grid\" role=\"group\">\n <div class=\"foldcaltg-head\">\n <span class=\"foldcaltg-corner\" aria-hidden=\"true\"></span>\n @for (column of grid().columns; track column.day.date) {\n <button\n type=\"button\"\n class=\"foldcaltg-dayhead\"\n [class.is-today]=\"column.day.isToday\"\n [class.is-weekend]=\"column.day.isWeekend\"\n [attr.aria-label]=\"columnLabel(column)\"\n [attr.aria-current]=\"column.day.isToday ? 'date' : null\"\n (click)=\"dayClick.emit(column.day.date)\"\n >\n <span class=\"foldcaltg-weekday\">{{ weekdayName(column) }}</span>\n <span class=\"foldcaltg-daynum\">{{ column.day.dayOfMonth }}</span>\n </button>\n }\n </div>\n\n <!-- The all-day strip: the same spanning bands the month grid draws. -->\n @if (grid().allDay.length > 0) {\n <div class=\"foldcaltg-allday\">\n <span class=\"foldcaltg-gutter-label\">{{ chrome.l().allDay }}</span>\n <div\n class=\"foldcaltg-strip\"\n [style.gridTemplateColumns]=\"\n 'repeat(' + grid().columns.length + ', 1fr)'\n \"\n [style.gridTemplateRows]=\"stripRows()\"\n >\n @for (band of grid().allDay; track band.key) {\n <button\n type=\"button\"\n class=\"foldcal-chip foldcaltg-band\"\n [attr.data-tone]=\"band.event.tone ?? 'neutral'\"\n [class.continues-before]=\"band.continuesBefore\"\n [class.continues-after]=\"band.continuesAfter\"\n [style.gridColumn]=\"\n band.startColumn + 1 + ' / ' + (band.endColumn + 2)\n \"\n [style.gridRow]=\"band.lane + 1\"\n (click)=\"eventClick.emit(band.event)\"\n >\n @if (chrome.eventContent(); as projected) {\n <ng-container\n [ngTemplateOutlet]=\"projected\"\n [ngTemplateOutletContext]=\"{\n $implicit: band.event,\n band: band,\n }\"\n />\n } @else {\n <span class=\"foldcal-chip-bar\"></span>\n <span class=\"foldcal-chip-body\">\n <span class=\"foldcal-chip-label\">{{ bandLabel(band) }}</span>\n </span>\n }\n </button>\n }\n </div>\n </div>\n }\n\n <!--\n The hours scroll, so the region is a tab stop with a name: a scrollable\n area a keyboard cannot reach is a scrollable area a keyboard user cannot\n read (WCAG 2.1.1). Found by axe, not by looking at it.\n -->\n <div\n class=\"foldcaltg-body\"\n tabindex=\"0\"\n role=\"group\"\n [attr.aria-label]=\"chrome.l().hours\"\n >\n <!--\n Positioned, not flowed: the label has to land on the same line as the rule\n it names, and a flex column only gets there by coincidence.\n -->\n <div class=\"foldcaltg-gutter\" aria-hidden=\"true\">\n @for (hour of grid().hours; track hour; let index = $index) {\n <span\n class=\"foldcaltg-hour\"\n [class.is-first]=\"index === 0\"\n [style.top.%]=\"(index / grid().hours.length) * 100\"\n >{{ hourLabel(hour) }}</span\n >\n }\n </div>\n\n <div\n class=\"foldcaltg-cols\"\n [style.gridTemplateColumns]=\"'repeat(' + grid().columns.length + ', 1fr)'\"\n >\n @for (column of grid().columns; track column.day.date) {\n <section\n class=\"foldcaltg-col\"\n [class.is-weekend]=\"column.day.isWeekend\"\n [attr.aria-label]=\"columnLabel(column)\"\n >\n <!-- Hour rules, drawn per column so a column can be styled alone. -->\n <span class=\"foldcaltg-rules\" aria-hidden=\"true\">\n @for (hour of grid().hours; track hour) {\n <span class=\"foldcaltg-rule\"></span>\n }\n </span>\n\n @if (column.day.isToday && nowOffset() !== null) {\n <span\n class=\"foldcaltg-now\"\n aria-hidden=\"true\"\n [style.top.%]=\"(nowOffset() ?? 0) * 100\"\n ></span>\n }\n\n @for (block of column.blocks; track block.key) {\n <button\n type=\"button\"\n class=\"foldcaltg-block\"\n [attr.data-tone]=\"block.event.tone ?? 'neutral'\"\n [class.continues-before]=\"block.continuesBefore\"\n [class.continues-after]=\"block.continuesAfter\"\n [attr.aria-label]=\"blockLabel(block)\"\n [style.top.%]=\"block.top * 100\"\n [style.height.%]=\"block.height * 100\"\n [style.insetInlineStart.%]=\"(block.column / block.columns) * 100\"\n [style.width.%]=\"(1 / block.columns) * 100\"\n (click)=\"eventClick.emit(block.event)\"\n >\n @if (chrome.eventContent(); as projected) {\n <ng-container\n [ngTemplateOutlet]=\"projected\"\n [ngTemplateOutletContext]=\"{\n $implicit: block.event,\n band: null,\n }\"\n />\n } @else {\n <span class=\"foldcaltg-block-bar\"></span>\n <!-- The glyph sits beside the label, never above it: a\n half-hour block is one line tall and the icon would take\n it. -->\n @if (block.event.icon; as icon) {\n <fold-icon\n class=\"foldcaltg-block-icon\"\n [name]=\"icon\"\n size=\"sm\"\n />\n }\n <span class=\"foldcaltg-block-body\">\n <span class=\"foldcaltg-block-label\">{{\n block.event.label\n }}</span>\n @if (block.event.subline) {\n <span class=\"foldcaltg-block-sub\">{{\n block.event.subline\n }}</span>\n }\n </span>\n }\n </button>\n }\n </section>\n }\n </div>\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}.foldcal-chip{position:relative;display:flex;align-items:stretch;gap:var(--fold-space-sm);width:100%;padding:var(--fold-space-xs) var(--fold-space-sm);padding-inline-start:0;border:0;border-radius:var(--fold-calendar-band-radius, var(--fold-radius-sm));background:var(--fold-color-surface-raised);color:var(--fold-color-text);font:inherit;text-align:start;cursor:pointer;overflow:hidden;transition:background-color var(--fold-motion-fast) ease}.foldcal-chip:hover{background:var(--fold-color-surface-hover)}.foldcal-chip:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px}.foldcal-chip-bar{flex:0 0 var(--fold-calendar-bar-width, 3px);align-self:stretch;background:var(--fold-color-text-muted)}.foldcal-chip-icon{flex:none;align-self:center;margin-inline-start:var(--fold-space-sm);color:var(--fold-color-text-secondary)}.foldcal-chip-body{display:flex;flex-direction:column;gap:1px;min-width:0}.foldcal-chip-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--fold-text-sm);font-weight:500}.foldcal-chip-sub{color:var(--fold-color-text-secondary);font-size:var(--fold-text-xs)}.foldcal-chip[data-tone=success] .foldcal-chip-bar{background:var(--fold-color-success)}.foldcal-chip[data-tone=warning] .foldcal-chip-bar{background:var(--fold-color-warning)}.foldcal-chip[data-tone=alert] .foldcal-chip-bar{background:var(--fold-color-alert)}.foldcal-chip[data-tone=muted] .foldcal-chip-bar{background:var(--fold-color-text-faded)}.foldcal-chip[data-tone=muted] .foldcal-chip-label{color:var(--fold-color-text-secondary);text-decoration:line-through;text-decoration-thickness:1px}@media(forced-colors:active){.foldcal-chip{border:1px solid CanvasText}.foldcal-chip-bar{forced-color-adjust:none}}@media(prefers-reduced-motion:reduce){.foldcal-chip{transition:none}}.foldcaltg{display:flex;flex-direction:column;min-height:0;border:1px solid var(--fold-color-border);border-radius:var(--fold-radius-md);background:var(--fold-color-surface-card);overflow:clip}.foldcaltg-head{display:grid;grid-template-columns:var(--fold-calendar-timegrid-gutter, 56px) repeat(auto-fit,minmax(0,1fr));grid-auto-flow:column;grid-auto-columns:minmax(0,1fr);background:var(--fold-color-surface-subtle);border-bottom:1px solid var(--fold-color-border)}.foldcaltg-corner{border-inline-end:1px solid var(--fold-color-border-subtle)}.foldcaltg-dayhead{display:flex;flex-direction:column;align-items:center;gap:1px;padding:var(--fold-space-sm) var(--fold-space-xs);border:0;border-inline-end:1px solid var(--fold-color-border-subtle);background:transparent;color:inherit;font:inherit;cursor:pointer}.foldcaltg-dayhead:last-child{border-inline-end:0}.foldcaltg-dayhead:hover{background:var(--fold-color-surface-hover)}.foldcaltg-dayhead:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px}.foldcaltg-dayhead.is-weekend{background:var(--fold-color-surface-sunken)}.foldcaltg-weekday{color:var(--fold-color-text-secondary);font-size:var(--fold-text-xs);font-weight:600;letter-spacing:.06em;text-transform:uppercase}.foldcaltg-daynum{color:var(--fold-color-text-secondary);font-size:var(--fold-text-md);font-variant-numeric:tabular-nums}.foldcaltg-dayhead.is-today{box-shadow:inset 0 2px 0 0 var(--fold-color-primary)}.foldcaltg-dayhead.is-today .foldcaltg-daynum{color:var(--fold-color-primary-text);font-weight:700}.foldcaltg-allday{display:grid;grid-template-columns:var(--fold-calendar-timegrid-gutter, 56px) 1fr;padding-block:var(--fold-space-xs);border-bottom:1px solid var(--fold-color-border)}.foldcaltg-gutter-label{padding-inline:var(--fold-space-xs);border-inline-end:1px solid var(--fold-color-border-subtle);color:var(--fold-color-text-secondary);font-size:var(--fold-text-xs);text-align:end}.foldcaltg-strip{display:grid;gap:1px}.foldcaltg-band{min-width:0;margin-inline:var(--fold-calendar-band-gutter, var(--fold-space-xs));padding-block:0;font-size:var(--fold-text-xs)}.foldcaltg-band.continues-before{margin-inline-start:0;border-start-start-radius:0;border-end-start-radius:0}.foldcaltg-band.continues-after{margin-inline-end:0;border-start-end-radius:0;border-end-end-radius:0}.foldcaltg-body{display:grid}.foldcaltg-body:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px}.foldcaltg-body{grid-template-columns:var(--fold-calendar-timegrid-gutter, 56px) 1fr;height:var(--fold-calendar-timegrid-height, 640px);overflow-y:auto}.foldcaltg-gutter{position:relative;border-inline-end:1px solid var(--fold-color-border-subtle)}.foldcaltg-hour{position:absolute;inset-inline:0;padding-inline-end:var(--fold-space-xs);color:var(--fold-color-text-secondary);font-size:var(--fold-text-xs);font-variant-numeric:tabular-nums;text-align:end;transform:translateY(-.5em)}.foldcaltg-hour.is-first{transform:none}.foldcaltg-cols{display:grid}.foldcaltg-col{position:relative;border-inline-end:1px solid var(--fold-color-border-subtle)}.foldcaltg-col:last-of-type{border-inline-end:0}.foldcaltg-col.is-weekend{background:var(--fold-color-surface-subtle)}.foldcaltg-rules{position:absolute;inset:0;display:flex;flex-direction:column}.foldcaltg-rule{flex:1;border-top:1px solid var(--fold-color-border-subtle)}.foldcaltg-now{position:absolute;inset-inline:0;z-index:2;height:2px;background:var(--fold-color-alert)}.foldcaltg-now:before{content:\"\";position:absolute;inset-inline-start:0;top:-3px;width:8px;height:8px;border-radius:50%;background:var(--fold-color-alert)}.foldcaltg-block{position:absolute;z-index:1;display:flex;align-items:stretch;gap:var(--fold-space-xs);min-height:14px;padding:0;padding-inline-end:var(--fold-space-xs);border:0;border-radius:var(--fold-calendar-band-radius, var(--fold-radius-sm));background:var(--fold-color-surface-raised);color:var(--fold-color-text);font:inherit;font-size:var(--fold-text-xs);text-align:start;cursor:pointer;overflow:hidden;transition:filter var(--fold-motion-fast) ease}.foldcaltg-block:hover{z-index:3;filter:brightness(1.08)}.foldcaltg-block:focus-visible{z-index:3;outline:2px solid var(--fold-color-primary);outline-offset:-2px}.foldcaltg-block.continues-before{border-start-start-radius:0;border-start-end-radius:0}.foldcaltg-block.continues-after{border-end-start-radius:0;border-end-end-radius:0}.foldcaltg-block-bar{flex:0 0 var(--fold-calendar-bar-width, 3px);align-self:stretch;background:var(--fold-color-text-muted)}.foldcaltg-block-body{display:flex;flex-direction:column;flex:1 1 auto;min-width:0;padding-block:1px}.foldcaltg-block-icon{flex:none;align-self:flex-start;margin-block-start:1px}.foldcaltg-block-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500}.foldcaltg-block-sub{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--fold-color-text-secondary)}.foldcaltg-block[data-tone=success]{background:var(--fold-color-success-surface);color:var(--fold-color-success-text)}.foldcaltg-block[data-tone=success] .foldcaltg-block-bar{background:var(--fold-color-success)}.foldcaltg-block[data-tone=warning]{background:var(--fold-color-warning-surface);color:var(--fold-color-warning-text)}.foldcaltg-block[data-tone=warning] .foldcaltg-block-bar{background:var(--fold-color-warning)}.foldcaltg-block[data-tone=alert]{background:var(--fold-color-alert-surface);color:var(--fold-color-alert-text)}.foldcaltg-block[data-tone=alert] .foldcaltg-block-bar{background:var(--fold-color-alert)}.foldcaltg-block[data-tone=muted]{background:transparent;color:var(--fold-color-text-secondary)}.foldcaltg-block[data-tone=muted] .foldcaltg-block-bar{background:var(--fold-color-text-faded)}.foldcaltg-block[data-tone=muted] .foldcaltg-block-label{color:var(--fold-color-text-secondary);text-decoration:line-through;text-decoration-thickness:1px}@media(forced-colors:active){.foldcaltg-block{border:1px solid CanvasText}.foldcaltg-block-bar{forced-color-adjust:none}}@media(forced-colors:active){.foldcaltg-dayhead.is-today{outline:2px solid Highlight;outline-offset:-2px}.foldcaltg-now{background:Highlight;forced-color-adjust:none}}@media(prefers-reduced-motion:reduce){.foldcaltg-block{transition:none}}@media print{.foldcaltg-body{height:auto;overflow:visible}.foldcaltg-block{border:1px solid GrayText;background:Canvas;color:CanvasText}.foldcaltg-block-bar{-webkit-print-color-adjust:exact;print-color-adjust:exact}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: FoldIconComponent, selector: "fold-icon", inputs: ["name", "size", "tone", "title"] }] });
6526
+ }
6527
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCalendarTimegridComponent, decorators: [{
6528
+ type: Component,
6529
+ args: [{ selector: "fold-calendar-timegrid", standalone: true, imports: [NgTemplateOutlet, FoldIconComponent], hostDirectives: [
6530
+ {
6531
+ directive: FoldCalendarChromeDirective,
6532
+ inputs: ["locale", "labels", "formats"],
6533
+ },
6534
+ ], template: "<div class=\"foldcaltg\" [attr.aria-label]=\"chrome.l().grid\" role=\"group\">\n <div class=\"foldcaltg-head\">\n <span class=\"foldcaltg-corner\" aria-hidden=\"true\"></span>\n @for (column of grid().columns; track column.day.date) {\n <button\n type=\"button\"\n class=\"foldcaltg-dayhead\"\n [class.is-today]=\"column.day.isToday\"\n [class.is-weekend]=\"column.day.isWeekend\"\n [attr.aria-label]=\"columnLabel(column)\"\n [attr.aria-current]=\"column.day.isToday ? 'date' : null\"\n (click)=\"dayClick.emit(column.day.date)\"\n >\n <span class=\"foldcaltg-weekday\">{{ weekdayName(column) }}</span>\n <span class=\"foldcaltg-daynum\">{{ column.day.dayOfMonth }}</span>\n </button>\n }\n </div>\n\n <!-- The all-day strip: the same spanning bands the month grid draws. -->\n @if (grid().allDay.length > 0) {\n <div class=\"foldcaltg-allday\">\n <span class=\"foldcaltg-gutter-label\">{{ chrome.l().allDay }}</span>\n <div\n class=\"foldcaltg-strip\"\n [style.gridTemplateColumns]=\"\n 'repeat(' + grid().columns.length + ', 1fr)'\n \"\n [style.gridTemplateRows]=\"stripRows()\"\n >\n @for (band of grid().allDay; track band.key) {\n <button\n type=\"button\"\n class=\"foldcal-chip foldcaltg-band\"\n [attr.data-tone]=\"band.event.tone ?? 'neutral'\"\n [class.continues-before]=\"band.continuesBefore\"\n [class.continues-after]=\"band.continuesAfter\"\n [style.gridColumn]=\"\n band.startColumn + 1 + ' / ' + (band.endColumn + 2)\n \"\n [style.gridRow]=\"band.lane + 1\"\n (click)=\"eventClick.emit(band.event)\"\n >\n @if (chrome.eventContent(); as projected) {\n <ng-container\n [ngTemplateOutlet]=\"projected\"\n [ngTemplateOutletContext]=\"{\n $implicit: band.event,\n band: band,\n }\"\n />\n } @else {\n <span class=\"foldcal-chip-bar\"></span>\n <span class=\"foldcal-chip-body\">\n <span class=\"foldcal-chip-label\">{{ bandLabel(band) }}</span>\n </span>\n }\n </button>\n }\n </div>\n </div>\n }\n\n <!--\n The hours scroll, so the region is a tab stop with a name: a scrollable\n area a keyboard cannot reach is a scrollable area a keyboard user cannot\n read (WCAG 2.1.1). Found by axe, not by looking at it.\n -->\n <div\n class=\"foldcaltg-body\"\n tabindex=\"0\"\n role=\"group\"\n [attr.aria-label]=\"chrome.l().hours\"\n >\n <!--\n Positioned, not flowed: the label has to land on the same line as the rule\n it names, and a flex column only gets there by coincidence.\n -->\n <div class=\"foldcaltg-gutter\" aria-hidden=\"true\">\n @for (hour of grid().hours; track hour; let index = $index) {\n <span\n class=\"foldcaltg-hour\"\n [class.is-first]=\"index === 0\"\n [style.top.%]=\"(index / grid().hours.length) * 100\"\n >{{ hourLabel(hour) }}</span\n >\n }\n </div>\n\n <div\n class=\"foldcaltg-cols\"\n [style.gridTemplateColumns]=\"'repeat(' + grid().columns.length + ', 1fr)'\"\n >\n @for (column of grid().columns; track column.day.date) {\n <section\n class=\"foldcaltg-col\"\n [class.is-weekend]=\"column.day.isWeekend\"\n [attr.aria-label]=\"columnLabel(column)\"\n >\n <!-- Hour rules, drawn per column so a column can be styled alone. -->\n <span class=\"foldcaltg-rules\" aria-hidden=\"true\">\n @for (hour of grid().hours; track hour) {\n <span class=\"foldcaltg-rule\"></span>\n }\n </span>\n\n @if (column.day.isToday && nowOffset() !== null) {\n <span\n class=\"foldcaltg-now\"\n aria-hidden=\"true\"\n [style.top.%]=\"(nowOffset() ?? 0) * 100\"\n ></span>\n }\n\n @for (block of column.blocks; track block.key) {\n <button\n type=\"button\"\n class=\"foldcaltg-block\"\n [attr.data-tone]=\"block.event.tone ?? 'neutral'\"\n [class.continues-before]=\"block.continuesBefore\"\n [class.continues-after]=\"block.continuesAfter\"\n [attr.aria-label]=\"blockLabel(block)\"\n [style.top.%]=\"block.top * 100\"\n [style.height.%]=\"block.height * 100\"\n [style.insetInlineStart.%]=\"(block.column / block.columns) * 100\"\n [style.width.%]=\"(1 / block.columns) * 100\"\n (click)=\"eventClick.emit(block.event)\"\n >\n @if (chrome.eventContent(); as projected) {\n <ng-container\n [ngTemplateOutlet]=\"projected\"\n [ngTemplateOutletContext]=\"{\n $implicit: block.event,\n band: null,\n }\"\n />\n } @else {\n <span class=\"foldcaltg-block-bar\"></span>\n <!-- The glyph sits beside the label, never above it: a\n half-hour block is one line tall and the icon would take\n it. -->\n @if (block.event.icon; as icon) {\n <fold-icon\n class=\"foldcaltg-block-icon\"\n [name]=\"icon\"\n size=\"sm\"\n />\n }\n <span class=\"foldcaltg-block-body\">\n <span class=\"foldcaltg-block-label\">{{\n block.event.label\n }}</span>\n @if (block.event.subline) {\n <span class=\"foldcaltg-block-sub\">{{\n block.event.subline\n }}</span>\n }\n </span>\n }\n </button>\n }\n </section>\n }\n </div>\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}.foldcal-chip{position:relative;display:flex;align-items:stretch;gap:var(--fold-space-sm);width:100%;padding:var(--fold-space-xs) var(--fold-space-sm);padding-inline-start:0;border:0;border-radius:var(--fold-calendar-band-radius, var(--fold-radius-sm));background:var(--fold-color-surface-raised);color:var(--fold-color-text);font:inherit;text-align:start;cursor:pointer;overflow:hidden;transition:background-color var(--fold-motion-fast) ease}.foldcal-chip:hover{background:var(--fold-color-surface-hover)}.foldcal-chip:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px}.foldcal-chip-bar{flex:0 0 var(--fold-calendar-bar-width, 3px);align-self:stretch;background:var(--fold-color-text-muted)}.foldcal-chip-icon{flex:none;align-self:center;margin-inline-start:var(--fold-space-sm);color:var(--fold-color-text-secondary)}.foldcal-chip-body{display:flex;flex-direction:column;gap:1px;min-width:0}.foldcal-chip-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--fold-text-sm);font-weight:500}.foldcal-chip-sub{color:var(--fold-color-text-secondary);font-size:var(--fold-text-xs)}.foldcal-chip[data-tone=success] .foldcal-chip-bar{background:var(--fold-color-success)}.foldcal-chip[data-tone=warning] .foldcal-chip-bar{background:var(--fold-color-warning)}.foldcal-chip[data-tone=alert] .foldcal-chip-bar{background:var(--fold-color-alert)}.foldcal-chip[data-tone=muted] .foldcal-chip-bar{background:var(--fold-color-text-faded)}.foldcal-chip[data-tone=muted] .foldcal-chip-label{color:var(--fold-color-text-secondary);text-decoration:line-through;text-decoration-thickness:1px}@media(forced-colors:active){.foldcal-chip{border:1px solid CanvasText}.foldcal-chip-bar{forced-color-adjust:none}}@media(prefers-reduced-motion:reduce){.foldcal-chip{transition:none}}.foldcaltg{display:flex;flex-direction:column;min-height:0;border:1px solid var(--fold-color-border);border-radius:var(--fold-radius-md);background:var(--fold-color-surface-card);overflow:clip}.foldcaltg-head{display:grid;grid-template-columns:var(--fold-calendar-timegrid-gutter, 56px) repeat(auto-fit,minmax(0,1fr));grid-auto-flow:column;grid-auto-columns:minmax(0,1fr);background:var(--fold-color-surface-subtle);border-bottom:1px solid var(--fold-color-border)}.foldcaltg-corner{border-inline-end:1px solid var(--fold-color-border-subtle)}.foldcaltg-dayhead{display:flex;flex-direction:column;align-items:center;gap:1px;padding:var(--fold-space-sm) var(--fold-space-xs);border:0;border-inline-end:1px solid var(--fold-color-border-subtle);background:transparent;color:inherit;font:inherit;cursor:pointer}.foldcaltg-dayhead:last-child{border-inline-end:0}.foldcaltg-dayhead:hover{background:var(--fold-color-surface-hover)}.foldcaltg-dayhead:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px}.foldcaltg-dayhead.is-weekend{background:var(--fold-color-surface-sunken)}.foldcaltg-weekday{color:var(--fold-color-text-secondary);font-size:var(--fold-text-xs);font-weight:600;letter-spacing:.06em;text-transform:uppercase}.foldcaltg-daynum{color:var(--fold-color-text-secondary);font-size:var(--fold-text-md);font-variant-numeric:tabular-nums}.foldcaltg-dayhead.is-today{box-shadow:inset 0 2px 0 0 var(--fold-color-primary)}.foldcaltg-dayhead.is-today .foldcaltg-daynum{color:var(--fold-color-primary-text);font-weight:700}.foldcaltg-allday{display:grid;grid-template-columns:var(--fold-calendar-timegrid-gutter, 56px) 1fr;padding-block:var(--fold-space-xs);border-bottom:1px solid var(--fold-color-border)}.foldcaltg-gutter-label{padding-inline:var(--fold-space-xs);border-inline-end:1px solid var(--fold-color-border-subtle);color:var(--fold-color-text-secondary);font-size:var(--fold-text-xs);text-align:end}.foldcaltg-strip{display:grid;gap:1px}.foldcaltg-band{min-width:0;margin-inline:var(--fold-calendar-band-gutter, var(--fold-space-xs));padding-block:0;font-size:var(--fold-text-xs)}.foldcaltg-band.continues-before{margin-inline-start:0;border-start-start-radius:0;border-end-start-radius:0}.foldcaltg-band.continues-after{margin-inline-end:0;border-start-end-radius:0;border-end-end-radius:0}.foldcaltg-body{display:grid}.foldcaltg-body:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px}.foldcaltg-body{grid-template-columns:var(--fold-calendar-timegrid-gutter, 56px) 1fr;height:var(--fold-calendar-timegrid-height, 640px);overflow-y:auto}.foldcaltg-gutter{position:relative;border-inline-end:1px solid var(--fold-color-border-subtle)}.foldcaltg-hour{position:absolute;inset-inline:0;padding-inline-end:var(--fold-space-xs);color:var(--fold-color-text-secondary);font-size:var(--fold-text-xs);font-variant-numeric:tabular-nums;text-align:end;transform:translateY(-.5em)}.foldcaltg-hour.is-first{transform:none}.foldcaltg-cols{display:grid}.foldcaltg-col{position:relative;border-inline-end:1px solid var(--fold-color-border-subtle)}.foldcaltg-col:last-of-type{border-inline-end:0}.foldcaltg-col.is-weekend{background:var(--fold-color-surface-subtle)}.foldcaltg-rules{position:absolute;inset:0;display:flex;flex-direction:column}.foldcaltg-rule{flex:1;border-top:1px solid var(--fold-color-border-subtle)}.foldcaltg-now{position:absolute;inset-inline:0;z-index:2;height:2px;background:var(--fold-color-alert)}.foldcaltg-now:before{content:\"\";position:absolute;inset-inline-start:0;top:-3px;width:8px;height:8px;border-radius:50%;background:var(--fold-color-alert)}.foldcaltg-block{position:absolute;z-index:1;display:flex;align-items:stretch;gap:var(--fold-space-xs);min-height:14px;padding:0;padding-inline-end:var(--fold-space-xs);border:0;border-radius:var(--fold-calendar-band-radius, var(--fold-radius-sm));background:var(--fold-color-surface-raised);color:var(--fold-color-text);font:inherit;font-size:var(--fold-text-xs);text-align:start;cursor:pointer;overflow:hidden;transition:filter var(--fold-motion-fast) ease}.foldcaltg-block:hover{z-index:3;filter:brightness(1.08)}.foldcaltg-block:focus-visible{z-index:3;outline:2px solid var(--fold-color-primary);outline-offset:-2px}.foldcaltg-block.continues-before{border-start-start-radius:0;border-start-end-radius:0}.foldcaltg-block.continues-after{border-end-start-radius:0;border-end-end-radius:0}.foldcaltg-block-bar{flex:0 0 var(--fold-calendar-bar-width, 3px);align-self:stretch;background:var(--fold-color-text-muted)}.foldcaltg-block-body{display:flex;flex-direction:column;flex:1 1 auto;min-width:0;padding-block:1px}.foldcaltg-block-icon{flex:none;align-self:flex-start;margin-block-start:1px}.foldcaltg-block-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500}.foldcaltg-block-sub{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--fold-color-text-secondary)}.foldcaltg-block[data-tone=success]{background:var(--fold-color-success-surface);color:var(--fold-color-success-text)}.foldcaltg-block[data-tone=success] .foldcaltg-block-bar{background:var(--fold-color-success)}.foldcaltg-block[data-tone=warning]{background:var(--fold-color-warning-surface);color:var(--fold-color-warning-text)}.foldcaltg-block[data-tone=warning] .foldcaltg-block-bar{background:var(--fold-color-warning)}.foldcaltg-block[data-tone=alert]{background:var(--fold-color-alert-surface);color:var(--fold-color-alert-text)}.foldcaltg-block[data-tone=alert] .foldcaltg-block-bar{background:var(--fold-color-alert)}.foldcaltg-block[data-tone=muted]{background:transparent;color:var(--fold-color-text-secondary)}.foldcaltg-block[data-tone=muted] .foldcaltg-block-bar{background:var(--fold-color-text-faded)}.foldcaltg-block[data-tone=muted] .foldcaltg-block-label{color:var(--fold-color-text-secondary);text-decoration:line-through;text-decoration-thickness:1px}@media(forced-colors:active){.foldcaltg-block{border:1px solid CanvasText}.foldcaltg-block-bar{forced-color-adjust:none}}@media(forced-colors:active){.foldcaltg-dayhead.is-today{outline:2px solid Highlight;outline-offset:-2px}.foldcaltg-now{background:Highlight;forced-color-adjust:none}}@media(prefers-reduced-motion:reduce){.foldcaltg-block{transition:none}}@media print{.foldcaltg-body{height:auto;overflow:visible}.foldcaltg-block{border:1px solid GrayText;background:Canvas;color:CanvasText}.foldcaltg-block-bar{-webkit-print-color-adjust:exact;print-color-adjust:exact}}\n"] }]
6535
+ }], propDecorators: { date: [{ type: i0.Input, args: [{ isSignal: true, alias: "date", required: true }] }], events: [{ type: i0.Input, args: [{ isSignal: true, alias: "events", required: false }] }], dayCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "dayCount", required: false }] }], today: [{ type: i0.Input, args: [{ isSignal: true, alias: "today", required: false }] }], now: [{ type: i0.Input, args: [{ isSignal: true, alias: "now", required: false }] }], dayStart: [{ type: i0.Input, args: [{ isSignal: true, alias: "dayStart", required: false }] }], dayEnd: [{ type: i0.Input, args: [{ isSignal: true, alias: "dayEnd", required: false }] }], weekStartsOn: [{ type: i0.Input, args: [{ isSignal: true, alias: "weekStartsOn", required: false }] }], weekendDays: [{ type: i0.Input, args: [{ isSignal: true, alias: "weekendDays", required: false }] }], maxAllDayLanes: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxAllDayLanes", required: false }] }], dayClick: [{ type: i0.Output, args: ["dayClick"] }], eventClick: [{ type: i0.Output, args: ["eventClick"] }] } });
6536
+
4251
6537
  /**
4252
6538
  * `<fold-choice-row>` — a horizontal single-select control.
4253
6539
  *
@@ -7282,13 +9568,13 @@ class FoldNavLayoutComponent {
7282
9568
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldNavLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7283
9569
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.8", type: FoldNavLayoutComponent, isStandalone: true, selector: "fold-nav-layout", inputs: { placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, foldAt: { classPropertyName: "foldAt", publicName: "foldAt", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.is-row": "!stacked()" } }, providers: [
7284
9570
  { provide: FOLD_NAV_LAYOUT, useExisting: FoldNavLayoutComponent },
7285
- ], exportAs: ["foldNavLayout"], ngImport: i0, template: "<div class=\"tl-nav\"><ng-content select=\"[tabNav]\" /></div>\n<div class=\"tl-body\"><ng-content /></div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--fold-nav-layout-gap, 16px);min-width:0}:host(.is-row){flex-direction:row}.tl-nav{display:grid;min-width:0}:host(.is-row) .tl-nav{flex:0 0 var(--fold-nav-layout-rail-width, var(--fold-rail-tertiary, 200px))}.tl-body{flex:1 1 auto;min-width:0}\n"] });
9571
+ ], exportAs: ["foldNavLayout"], ngImport: i0, template: "<div class=\"tl-nav\"><ng-content select=\"[tabNav]\" /></div>\n<div class=\"tl-body\"><ng-content /></div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--fold-nav-layout-gap, var(--fold-space-lg, 16px));min-width:0}:host(.is-row){flex-direction:row}.tl-nav{display:grid;min-width:0}:host(.is-row) .tl-nav{flex:0 0 var(--fold-nav-layout-rail-width, var(--fold-rail-tertiary, 200px))}.tl-body{flex:1 1 auto;min-width:0}\n"] });
7286
9572
  }
7287
9573
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldNavLayoutComponent, decorators: [{
7288
9574
  type: Component,
7289
9575
  args: [{ selector: "fold-nav-layout", standalone: true, exportAs: "foldNavLayout", host: { "[class.is-row]": "!stacked()" }, providers: [
7290
9576
  { provide: FOLD_NAV_LAYOUT, useExisting: FoldNavLayoutComponent },
7291
- ], template: "<div class=\"tl-nav\"><ng-content select=\"[tabNav]\" /></div>\n<div class=\"tl-body\"><ng-content /></div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--fold-nav-layout-gap, 16px);min-width:0}:host(.is-row){flex-direction:row}.tl-nav{display:grid;min-width:0}:host(.is-row) .tl-nav{flex:0 0 var(--fold-nav-layout-rail-width, var(--fold-rail-tertiary, 200px))}.tl-body{flex:1 1 auto;min-width:0}\n"] }]
9577
+ ], template: "<div class=\"tl-nav\"><ng-content select=\"[tabNav]\" /></div>\n<div class=\"tl-body\"><ng-content /></div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--fold-nav-layout-gap, var(--fold-space-lg, 16px));min-width:0}:host(.is-row){flex-direction:row}.tl-nav{display:grid;min-width:0}:host(.is-row) .tl-nav{flex:0 0 var(--fold-nav-layout-rail-width, var(--fold-rail-tertiary, 200px))}.tl-body{flex:1 1 auto;min-width:0}\n"] }]
7292
9578
  }], ctorParameters: () => [], propDecorators: { placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], foldAt: [{ type: i0.Input, args: [{ isSignal: true, alias: "foldAt", required: false }] }] } });
7293
9579
 
7294
9580
  /**
@@ -7871,6 +10157,13 @@ class FoldViewNavComponent {
7871
10157
  ...(ngDevMode ? [{ debugName: "background" }] : /* istanbul ignore next */ []));
7872
10158
  /** The nearest layout, if any — lets `direction="auto"` follow it. */
7873
10159
  layout = inject(FOLD_NAV_LAYOUT, { optional: true });
10160
+ /**
10161
+ * No wrapping `fold-nav-layout`. Standalone, the bar owns its gap to the
10162
+ * following content; inside a layout, the layout owns it (avoiding a double
10163
+ * gap).
10164
+ */
10165
+ isStandalone = computed(() => this.layout === null, /* @ts-ignore */
10166
+ ...(ngDevMode ? [{ debugName: "isStandalone" }] : /* istanbul ignore next */ []));
7874
10167
  /** `direction` with `auto` resolved against the wrapping layout. */
7875
10168
  resolvedDirection = computed(() => {
7876
10169
  const d = this.direction();
@@ -7881,7 +10174,7 @@ class FoldViewNavComponent {
7881
10174
  }, /* @ts-ignore */
7882
10175
  ...(ngDevMode ? [{ debugName: "resolvedDirection" }] : /* istanbul ignore next */ []));
7883
10176
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldViewNavComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7884
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldViewNavComponent, isStandalone: true, selector: "fold-view-nav", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, activeKey: { classPropertyName: "activeKey", publicName: "activeKey", isSignal: true, isRequired: false, transformFunction: null }, activeStyle: { classPropertyName: "activeStyle", publicName: "activeStyle", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, background: { classPropertyName: "background", publicName: "background", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeKey: "activeKeyChange" }, ngImport: i0, template: "<nav\n class=\"tab-bar\"\n [class.style-underline]=\"activeStyle() === 'underline'\"\n [class.style-fill]=\"activeStyle() === 'fill'\"\n [class.dir-vertical]=\"resolvedDirection() === 'vertical'\"\n [class.size-comfortable]=\"size() === 'comfortable'\"\n [class.is-collapsed]=\"collapsed()\"\n [class.bg-surface]=\"background() === 'surface'\"\n>\n @for (item of items(); track item.key) {\n @if (item.disabled) {\n <button\n type=\"button\"\n class=\"tab-bar-item is-disabled\"\n disabled\n aria-disabled=\"true\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{ $implicit: item, active: false }\"\n />\n </button>\n } @else if (item.link !== undefined) {\n <a\n class=\"tab-bar-item\"\n [routerLink]=\"item.link\"\n routerLinkActive=\"is-active\"\n #rla=\"routerLinkActive\"\n [attr.aria-current]=\"rla.isActive ? 'page' : null\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{ $implicit: item, active: rla.isActive }\"\n />\n </a>\n } @else if (item.href !== undefined) {\n <a\n class=\"tab-bar-item\"\n [href]=\"item.href\"\n [class.is-active]=\"activeKey() === item.key\"\n [attr.aria-current]=\"activeKey() === item.key ? 'page' : null\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n active: activeKey() === item.key,\n }\"\n />\n </a>\n } @else {\n <button\n type=\"button\"\n class=\"tab-bar-item\"\n [class.is-active]=\"activeKey() === item.key\"\n [attr.aria-current]=\"activeKey() === item.key ? 'page' : null\"\n (click)=\"activeKey.set(item.key)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n active: activeKey() === item.key,\n }\"\n />\n </button>\n }\n }\n</nav>\n\n<!-- One item's inner content, shared by the link / button branches. -->\n<ng-template #body let-item let-active=\"active\">\n @if (item.icon; as ic) {\n <fold-icon class=\"tab-bar-icon\" [name]=\"ic\" size=\"sm\" />\n } @else if (collapsed()) {\n <!-- Collapsed needs a fixed glyph; an icon-less item falls back to its\n initial so the rail stays icon-width and the label still tooltips. -->\n <span class=\"tab-bar-icon tab-bar-initial\" aria-hidden=\"true\">{{\n item.label.charAt(0)\n }}</span>\n }\n <span class=\"tab-bar-label\">{{ item.label }}</span>\n @if (item.badge !== undefined && item.badge !== null) {\n <fold-badge\n class=\"tab-bar-badge\"\n [content]=\"item.badge + ''\"\n [variant]=\"active ? 'accent' : 'neutral'\"\n />\n }\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:flex;width:100%;max-width:100%;overflow:hidden}.tab-bar{flex:1;display:flex;max-width:100%;gap:2px;-webkit-user-select:none;user-select:none}.bg-surface{background:var(--fold-color-bg-rail-tertiary);padding:var(--fold-space-xs) var(--fold-space-sm) 0}.bg-surface.dir-vertical{padding:var(--fold-space-md) var(--fold-space-sm)}.tab-bar-item{flex:1;display:flex;align-items:center;justify-content:center;gap:var(--fold-space-sm);padding:var(--fold-space-sm) var(--fold-space-sm);background:none;border:none;color:var(--fold-color-text-muted);font-family:inherit;font-size:10px;font-weight:600;cursor:pointer;text-align:center;text-decoration:none;transition:color .1s ease,background .1s ease,border-color .1s ease;white-space:nowrap}.tab-bar-item:hover{color:var(--fold-color-text-secondary)}.tab-bar-item.is-disabled{opacity:.5;cursor:default;pointer-events:none}.size-comfortable .tab-bar-item{flex:0 1 auto;gap:var(--fold-space-sm);padding:var(--fold-space-md) var(--fold-space-lg);font-size:var(--fold-text-sm)}.dir-vertical.size-comfortable .tab-bar-item{padding:var(--fold-space-md) var(--fold-space-md);font-size:var(--fold-text-md)}.style-underline{border-bottom:1px solid var(--fold-color-border)}.style-underline .tab-bar-item{border-bottom:2px solid transparent;margin-bottom:-1px}.style-underline .tab-bar-item.is-active{color:var(--fold-color-text);border-bottom-color:var(--fold-color-primary)}.style-fill .tab-bar-item{border-radius:var(--fold-radius-sm);border:1px solid transparent}.style-fill .tab-bar-item.is-active{background:var(--fold-color-primary-surface);color:var(--fold-color-primary-text);font-weight:600}.style-fill .tab-bar-item:hover:not(.is-active){background:var(--fold-color-surface-hover);color:var(--fold-color-text)}.dir-vertical{flex-direction:column}.dir-vertical .tab-bar-item{flex:none;justify-content:flex-start;text-align:left;padding:var(--fold-space-sm) var(--fold-space-md);gap:var(--fold-space-sm);border-radius:var(--fold-radius-sm);font-size:var(--fold-text-sm);font-weight:500}.dir-vertical.style-underline{border-bottom:none;border-right:1px solid var(--fold-color-border)}.dir-vertical.style-underline .tab-bar-item{border-bottom:none;border-left:2px solid transparent;margin-bottom:0;margin-right:-1px}.dir-vertical.style-underline .tab-bar-item.is-active{border-left-color:var(--fold-color-primary)}.dir-vertical.style-fill .tab-bar-item.is-active{background:var(--fold-color-primary-surface);color:var(--fold-color-primary-text);font-weight:600}.is-collapsed .tab-bar-item{position:relative;flex:0 0 auto;gap:0}.is-collapsed .tab-bar-item:not(.is-active) .tab-bar-badge{display:none}.is-collapsed .tab-bar-item.is-active{flex:1 1 auto;min-width:0;gap:var(--fold-space-sm)}.is-collapsed .tab-bar-item.is-active .tab-bar-label{overflow:hidden;text-overflow:ellipsis}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active) .tab-bar-icon~.tab-bar-label{position:absolute;padding:var(--fold-space-xs) var(--fold-space-sm);border-radius:var(--fold-radius-sm);background:var(--fold-color-glass);border:1px solid var(--fold-color-glass-border);color:var(--fold-color-text);font-size:var(--fold-text-xs);font-weight:500;white-space:nowrap;opacity:0;pointer-events:none;box-shadow:var(--fold-shadow-md);transition:opacity var(--fold-motion-fast);z-index:100}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-icon~.tab-bar-label{left:calc(100% + 8px);top:50%;transform:translateY(-50%)}.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active) .tab-bar-icon~.tab-bar-label{top:calc(100% + 6px);left:50%;transform:translate(-50%)}.dir-vertical.is-collapsed .tab-bar-item:hover .tab-bar-icon~.tab-bar-label,.dir-vertical.is-collapsed .tab-bar-item:focus-visible .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active):hover .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active):focus-visible .tab-bar-icon~.tab-bar-label{opacity:1}.dir-vertical.is-collapsed{align-items:center}.dir-vertical.is-collapsed .tab-bar-item,.dir-vertical.is-collapsed .tab-bar-item.is-active{justify-content:center;gap:0}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-badge{display:block;position:absolute;top:-2px;right:-2px;transform:scale(.85);transform-origin:top right;pointer-events:none}:host:has(.is-collapsed){overflow:visible}.tab-bar-icon{flex-shrink:0}.tab-bar-initial{display:grid;place-items:center;width:18px;height:18px;border-radius:var(--fold-radius-sm);font-size:11px;font-weight:700;line-height:1;text-transform:uppercase}.tab-bar-badge{flex-shrink:0;font-variant-numeric:tabular-nums}.tab-bar-item:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px;border-radius:var(--fold-radius-sm)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: FoldIconComponent, selector: "fold-icon", inputs: ["name", "size", "tone", "title"] }, { kind: "component", type: FoldBadgeComponent, selector: "fold-badge", inputs: ["content", "radius", "variant"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }] });
10177
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldViewNavComponent, isStandalone: true, selector: "fold-view-nav", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, activeKey: { classPropertyName: "activeKey", publicName: "activeKey", isSignal: true, isRequired: false, transformFunction: null }, activeStyle: { classPropertyName: "activeStyle", publicName: "activeStyle", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, background: { classPropertyName: "background", publicName: "background", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeKey: "activeKeyChange" }, host: { properties: { "class.is-standalone": "isStandalone()", "class.is-horizontal": "resolvedDirection() === 'horizontal'" } }, ngImport: i0, template: "<nav\n class=\"tab-bar\"\n [class.style-underline]=\"activeStyle() === 'underline'\"\n [class.style-fill]=\"activeStyle() === 'fill'\"\n [class.dir-vertical]=\"resolvedDirection() === 'vertical'\"\n [class.size-comfortable]=\"size() === 'comfortable'\"\n [class.is-collapsed]=\"collapsed()\"\n [class.bg-surface]=\"background() === 'surface'\"\n>\n @for (item of items(); track item.key) {\n @if (item.disabled) {\n <button\n type=\"button\"\n class=\"tab-bar-item is-disabled\"\n disabled\n aria-disabled=\"true\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{ $implicit: item, active: false }\"\n />\n </button>\n } @else if (item.link !== undefined) {\n <a\n class=\"tab-bar-item\"\n [routerLink]=\"item.link\"\n routerLinkActive=\"is-active\"\n #rla=\"routerLinkActive\"\n [attr.aria-current]=\"rla.isActive ? 'page' : null\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{ $implicit: item, active: rla.isActive }\"\n />\n </a>\n } @else if (item.href !== undefined) {\n <a\n class=\"tab-bar-item\"\n [href]=\"item.href\"\n [class.is-active]=\"activeKey() === item.key\"\n [attr.aria-current]=\"activeKey() === item.key ? 'page' : null\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n active: activeKey() === item.key,\n }\"\n />\n </a>\n } @else {\n <button\n type=\"button\"\n class=\"tab-bar-item\"\n [class.is-active]=\"activeKey() === item.key\"\n [attr.aria-current]=\"activeKey() === item.key ? 'page' : null\"\n (click)=\"activeKey.set(item.key)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n active: activeKey() === item.key,\n }\"\n />\n </button>\n }\n }\n</nav>\n\n<!-- One item's inner content, shared by the link / button branches. -->\n<ng-template #body let-item let-active=\"active\">\n @if (item.icon; as ic) {\n <fold-icon class=\"tab-bar-icon\" [name]=\"ic\" size=\"sm\" />\n } @else if (collapsed()) {\n <!-- Collapsed needs a fixed glyph; an icon-less item falls back to its\n initial so the rail stays icon-width and the label still tooltips. -->\n <span class=\"tab-bar-icon tab-bar-initial\" aria-hidden=\"true\">{{\n item.label.charAt(0)\n }}</span>\n }\n <span class=\"tab-bar-label\">{{ item.label }}</span>\n @if (item.badge !== undefined && item.badge !== null) {\n <fold-badge\n class=\"tab-bar-badge\"\n [content]=\"item.badge + ''\"\n [variant]=\"active ? 'accent' : 'neutral'\"\n />\n }\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:flex;width:100%;max-width:100%;overflow:hidden}.tab-bar{flex:1;display:flex;max-width:100%;gap:2px;-webkit-user-select:none;user-select:none}.bg-surface{background:var(--fold-color-bg-rail-tertiary);padding:var(--fold-space-xs) var(--fold-space-sm) 0}.bg-surface.dir-vertical{padding:var(--fold-space-md) var(--fold-space-sm)}.tab-bar-item{flex:1;display:flex;align-items:center;justify-content:center;gap:var(--fold-space-sm);padding:var(--fold-space-sm) var(--fold-space-sm);background:none;border:none;color:var(--fold-color-text-muted);font-family:inherit;font-size:10px;font-weight:600;cursor:pointer;text-align:center;text-decoration:none;transition:color .1s ease,background .1s ease,border-color .1s ease;white-space:nowrap}.tab-bar-item:hover{color:var(--fold-color-text-secondary)}.tab-bar-item.is-disabled{opacity:.5;cursor:default;pointer-events:none}.size-comfortable .tab-bar-item{flex:0 1 auto;gap:var(--fold-space-sm);padding:var(--fold-space-md) var(--fold-space-lg);font-size:var(--fold-text-sm)}.dir-vertical.size-comfortable .tab-bar-item{padding:var(--fold-space-md) var(--fold-space-md);font-size:var(--fold-text-md)}.style-underline{border-bottom:1px solid var(--fold-color-border)}.style-underline .tab-bar-item{border-bottom:2px solid transparent;margin-bottom:-1px}.style-underline .tab-bar-item.is-active{color:var(--fold-color-text);border-bottom-color:var(--fold-color-primary)}.style-fill .tab-bar-item{border-radius:var(--fold-radius-sm);border:1px solid transparent}.style-fill .tab-bar-item.is-active{background:var(--fold-color-primary-surface);color:var(--fold-color-primary-text);font-weight:600}.style-fill .tab-bar-item:hover:not(.is-active){background:var(--fold-color-surface-hover);color:var(--fold-color-text)}.dir-vertical{flex-direction:column}.dir-vertical .tab-bar-item{flex:none;justify-content:flex-start;text-align:left;padding:var(--fold-space-sm) var(--fold-space-md);gap:var(--fold-space-sm);border-radius:var(--fold-radius-sm);font-size:var(--fold-text-sm);font-weight:500}.dir-vertical.style-underline{border-bottom:none;border-right:1px solid var(--fold-color-border)}.dir-vertical.style-underline .tab-bar-item{border-bottom:none;border-left:2px solid transparent;margin-bottom:0;margin-right:-1px}.dir-vertical.style-underline .tab-bar-item.is-active{border-left-color:var(--fold-color-primary)}.dir-vertical.style-fill .tab-bar-item.is-active{background:var(--fold-color-primary-surface);color:var(--fold-color-primary-text);font-weight:600}.is-collapsed .tab-bar-item{position:relative;flex:0 0 auto;gap:0}.is-collapsed .tab-bar-item:not(.is-active) .tab-bar-badge{display:none}.is-collapsed .tab-bar-item.is-active{flex:1 1 auto;min-width:0;gap:var(--fold-space-sm)}.is-collapsed .tab-bar-item.is-active .tab-bar-label{overflow:hidden;text-overflow:ellipsis}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active) .tab-bar-icon~.tab-bar-label{position:absolute;padding:var(--fold-space-xs) var(--fold-space-sm);border-radius:var(--fold-radius-sm);background:var(--fold-color-glass);border:1px solid var(--fold-color-glass-border);color:var(--fold-color-text);font-size:var(--fold-text-xs);font-weight:500;white-space:nowrap;opacity:0;pointer-events:none;box-shadow:var(--fold-shadow-md);transition:opacity var(--fold-motion-fast);z-index:100}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-icon~.tab-bar-label{left:calc(100% + 8px);top:50%;transform:translateY(-50%)}.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active) .tab-bar-icon~.tab-bar-label{top:calc(100% + 6px);left:50%;transform:translate(-50%)}.dir-vertical.is-collapsed .tab-bar-item:hover .tab-bar-icon~.tab-bar-label,.dir-vertical.is-collapsed .tab-bar-item:focus-visible .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active):hover .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active):focus-visible .tab-bar-icon~.tab-bar-label{opacity:1}.dir-vertical.is-collapsed{align-items:center}.dir-vertical.is-collapsed .tab-bar-item,.dir-vertical.is-collapsed .tab-bar-item.is-active{justify-content:center;gap:0}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-badge{display:block;position:absolute;top:-2px;right:-2px;transform:scale(.85);transform-origin:top right;pointer-events:none}:host:has(.is-collapsed){overflow:visible}.tab-bar-icon{flex-shrink:0}.tab-bar-initial{display:grid;place-items:center;width:18px;height:18px;border-radius:var(--fold-radius-sm);font-size:11px;font-weight:700;line-height:1;text-transform:uppercase}.tab-bar-badge{flex-shrink:0;font-variant-numeric:tabular-nums}.tab-bar-item:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px;border-radius:var(--fold-radius-sm)}:host(.is-standalone.is-horizontal){margin-block-end:var(--fold-nav-layout-gap, var(--fold-space-lg, 16px))}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: FoldIconComponent, selector: "fold-icon", inputs: ["name", "size", "tone", "title"] }, { kind: "component", type: FoldBadgeComponent, selector: "fold-badge", inputs: ["content", "radius", "variant"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }] });
7885
10178
  }
7886
10179
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldViewNavComponent, decorators: [{
7887
10180
  type: Component,
@@ -7891,7 +10184,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
7891
10184
  FoldBadgeComponent,
7892
10185
  RouterLink,
7893
10186
  RouterLinkActive,
7894
- ], template: "<nav\n class=\"tab-bar\"\n [class.style-underline]=\"activeStyle() === 'underline'\"\n [class.style-fill]=\"activeStyle() === 'fill'\"\n [class.dir-vertical]=\"resolvedDirection() === 'vertical'\"\n [class.size-comfortable]=\"size() === 'comfortable'\"\n [class.is-collapsed]=\"collapsed()\"\n [class.bg-surface]=\"background() === 'surface'\"\n>\n @for (item of items(); track item.key) {\n @if (item.disabled) {\n <button\n type=\"button\"\n class=\"tab-bar-item is-disabled\"\n disabled\n aria-disabled=\"true\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{ $implicit: item, active: false }\"\n />\n </button>\n } @else if (item.link !== undefined) {\n <a\n class=\"tab-bar-item\"\n [routerLink]=\"item.link\"\n routerLinkActive=\"is-active\"\n #rla=\"routerLinkActive\"\n [attr.aria-current]=\"rla.isActive ? 'page' : null\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{ $implicit: item, active: rla.isActive }\"\n />\n </a>\n } @else if (item.href !== undefined) {\n <a\n class=\"tab-bar-item\"\n [href]=\"item.href\"\n [class.is-active]=\"activeKey() === item.key\"\n [attr.aria-current]=\"activeKey() === item.key ? 'page' : null\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n active: activeKey() === item.key,\n }\"\n />\n </a>\n } @else {\n <button\n type=\"button\"\n class=\"tab-bar-item\"\n [class.is-active]=\"activeKey() === item.key\"\n [attr.aria-current]=\"activeKey() === item.key ? 'page' : null\"\n (click)=\"activeKey.set(item.key)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n active: activeKey() === item.key,\n }\"\n />\n </button>\n }\n }\n</nav>\n\n<!-- One item's inner content, shared by the link / button branches. -->\n<ng-template #body let-item let-active=\"active\">\n @if (item.icon; as ic) {\n <fold-icon class=\"tab-bar-icon\" [name]=\"ic\" size=\"sm\" />\n } @else if (collapsed()) {\n <!-- Collapsed needs a fixed glyph; an icon-less item falls back to its\n initial so the rail stays icon-width and the label still tooltips. -->\n <span class=\"tab-bar-icon tab-bar-initial\" aria-hidden=\"true\">{{\n item.label.charAt(0)\n }}</span>\n }\n <span class=\"tab-bar-label\">{{ item.label }}</span>\n @if (item.badge !== undefined && item.badge !== null) {\n <fold-badge\n class=\"tab-bar-badge\"\n [content]=\"item.badge + ''\"\n [variant]=\"active ? 'accent' : 'neutral'\"\n />\n }\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:flex;width:100%;max-width:100%;overflow:hidden}.tab-bar{flex:1;display:flex;max-width:100%;gap:2px;-webkit-user-select:none;user-select:none}.bg-surface{background:var(--fold-color-bg-rail-tertiary);padding:var(--fold-space-xs) var(--fold-space-sm) 0}.bg-surface.dir-vertical{padding:var(--fold-space-md) var(--fold-space-sm)}.tab-bar-item{flex:1;display:flex;align-items:center;justify-content:center;gap:var(--fold-space-sm);padding:var(--fold-space-sm) var(--fold-space-sm);background:none;border:none;color:var(--fold-color-text-muted);font-family:inherit;font-size:10px;font-weight:600;cursor:pointer;text-align:center;text-decoration:none;transition:color .1s ease,background .1s ease,border-color .1s ease;white-space:nowrap}.tab-bar-item:hover{color:var(--fold-color-text-secondary)}.tab-bar-item.is-disabled{opacity:.5;cursor:default;pointer-events:none}.size-comfortable .tab-bar-item{flex:0 1 auto;gap:var(--fold-space-sm);padding:var(--fold-space-md) var(--fold-space-lg);font-size:var(--fold-text-sm)}.dir-vertical.size-comfortable .tab-bar-item{padding:var(--fold-space-md) var(--fold-space-md);font-size:var(--fold-text-md)}.style-underline{border-bottom:1px solid var(--fold-color-border)}.style-underline .tab-bar-item{border-bottom:2px solid transparent;margin-bottom:-1px}.style-underline .tab-bar-item.is-active{color:var(--fold-color-text);border-bottom-color:var(--fold-color-primary)}.style-fill .tab-bar-item{border-radius:var(--fold-radius-sm);border:1px solid transparent}.style-fill .tab-bar-item.is-active{background:var(--fold-color-primary-surface);color:var(--fold-color-primary-text);font-weight:600}.style-fill .tab-bar-item:hover:not(.is-active){background:var(--fold-color-surface-hover);color:var(--fold-color-text)}.dir-vertical{flex-direction:column}.dir-vertical .tab-bar-item{flex:none;justify-content:flex-start;text-align:left;padding:var(--fold-space-sm) var(--fold-space-md);gap:var(--fold-space-sm);border-radius:var(--fold-radius-sm);font-size:var(--fold-text-sm);font-weight:500}.dir-vertical.style-underline{border-bottom:none;border-right:1px solid var(--fold-color-border)}.dir-vertical.style-underline .tab-bar-item{border-bottom:none;border-left:2px solid transparent;margin-bottom:0;margin-right:-1px}.dir-vertical.style-underline .tab-bar-item.is-active{border-left-color:var(--fold-color-primary)}.dir-vertical.style-fill .tab-bar-item.is-active{background:var(--fold-color-primary-surface);color:var(--fold-color-primary-text);font-weight:600}.is-collapsed .tab-bar-item{position:relative;flex:0 0 auto;gap:0}.is-collapsed .tab-bar-item:not(.is-active) .tab-bar-badge{display:none}.is-collapsed .tab-bar-item.is-active{flex:1 1 auto;min-width:0;gap:var(--fold-space-sm)}.is-collapsed .tab-bar-item.is-active .tab-bar-label{overflow:hidden;text-overflow:ellipsis}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active) .tab-bar-icon~.tab-bar-label{position:absolute;padding:var(--fold-space-xs) var(--fold-space-sm);border-radius:var(--fold-radius-sm);background:var(--fold-color-glass);border:1px solid var(--fold-color-glass-border);color:var(--fold-color-text);font-size:var(--fold-text-xs);font-weight:500;white-space:nowrap;opacity:0;pointer-events:none;box-shadow:var(--fold-shadow-md);transition:opacity var(--fold-motion-fast);z-index:100}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-icon~.tab-bar-label{left:calc(100% + 8px);top:50%;transform:translateY(-50%)}.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active) .tab-bar-icon~.tab-bar-label{top:calc(100% + 6px);left:50%;transform:translate(-50%)}.dir-vertical.is-collapsed .tab-bar-item:hover .tab-bar-icon~.tab-bar-label,.dir-vertical.is-collapsed .tab-bar-item:focus-visible .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active):hover .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active):focus-visible .tab-bar-icon~.tab-bar-label{opacity:1}.dir-vertical.is-collapsed{align-items:center}.dir-vertical.is-collapsed .tab-bar-item,.dir-vertical.is-collapsed .tab-bar-item.is-active{justify-content:center;gap:0}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-badge{display:block;position:absolute;top:-2px;right:-2px;transform:scale(.85);transform-origin:top right;pointer-events:none}:host:has(.is-collapsed){overflow:visible}.tab-bar-icon{flex-shrink:0}.tab-bar-initial{display:grid;place-items:center;width:18px;height:18px;border-radius:var(--fold-radius-sm);font-size:11px;font-weight:700;line-height:1;text-transform:uppercase}.tab-bar-badge{flex-shrink:0;font-variant-numeric:tabular-nums}.tab-bar-item:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px;border-radius:var(--fold-radius-sm)}\n"] }]
10187
+ ], host: {
10188
+ // A standalone horizontal bar owns the gap to the content it heads, matching
10189
+ // fold-nav-layout. Inside a layout, the layout owns that gap (these two
10190
+ // classes never both apply there); the SCSS keys the margin on their
10191
+ // conjunction. Vertical is a side rail, not a header — no block-end margin.
10192
+ "[class.is-standalone]": "isStandalone()",
10193
+ "[class.is-horizontal]": "resolvedDirection() === 'horizontal'",
10194
+ }, template: "<nav\n class=\"tab-bar\"\n [class.style-underline]=\"activeStyle() === 'underline'\"\n [class.style-fill]=\"activeStyle() === 'fill'\"\n [class.dir-vertical]=\"resolvedDirection() === 'vertical'\"\n [class.size-comfortable]=\"size() === 'comfortable'\"\n [class.is-collapsed]=\"collapsed()\"\n [class.bg-surface]=\"background() === 'surface'\"\n>\n @for (item of items(); track item.key) {\n @if (item.disabled) {\n <button\n type=\"button\"\n class=\"tab-bar-item is-disabled\"\n disabled\n aria-disabled=\"true\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{ $implicit: item, active: false }\"\n />\n </button>\n } @else if (item.link !== undefined) {\n <a\n class=\"tab-bar-item\"\n [routerLink]=\"item.link\"\n routerLinkActive=\"is-active\"\n #rla=\"routerLinkActive\"\n [attr.aria-current]=\"rla.isActive ? 'page' : null\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{ $implicit: item, active: rla.isActive }\"\n />\n </a>\n } @else if (item.href !== undefined) {\n <a\n class=\"tab-bar-item\"\n [href]=\"item.href\"\n [class.is-active]=\"activeKey() === item.key\"\n [attr.aria-current]=\"activeKey() === item.key ? 'page' : null\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n active: activeKey() === item.key,\n }\"\n />\n </a>\n } @else {\n <button\n type=\"button\"\n class=\"tab-bar-item\"\n [class.is-active]=\"activeKey() === item.key\"\n [attr.aria-current]=\"activeKey() === item.key ? 'page' : null\"\n (click)=\"activeKey.set(item.key)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n active: activeKey() === item.key,\n }\"\n />\n </button>\n }\n }\n</nav>\n\n<!-- One item's inner content, shared by the link / button branches. -->\n<ng-template #body let-item let-active=\"active\">\n @if (item.icon; as ic) {\n <fold-icon class=\"tab-bar-icon\" [name]=\"ic\" size=\"sm\" />\n } @else if (collapsed()) {\n <!-- Collapsed needs a fixed glyph; an icon-less item falls back to its\n initial so the rail stays icon-width and the label still tooltips. -->\n <span class=\"tab-bar-icon tab-bar-initial\" aria-hidden=\"true\">{{\n item.label.charAt(0)\n }}</span>\n }\n <span class=\"tab-bar-label\">{{ item.label }}</span>\n @if (item.badge !== undefined && item.badge !== null) {\n <fold-badge\n class=\"tab-bar-badge\"\n [content]=\"item.badge + ''\"\n [variant]=\"active ? 'accent' : 'neutral'\"\n />\n }\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:flex;width:100%;max-width:100%;overflow:hidden}.tab-bar{flex:1;display:flex;max-width:100%;gap:2px;-webkit-user-select:none;user-select:none}.bg-surface{background:var(--fold-color-bg-rail-tertiary);padding:var(--fold-space-xs) var(--fold-space-sm) 0}.bg-surface.dir-vertical{padding:var(--fold-space-md) var(--fold-space-sm)}.tab-bar-item{flex:1;display:flex;align-items:center;justify-content:center;gap:var(--fold-space-sm);padding:var(--fold-space-sm) var(--fold-space-sm);background:none;border:none;color:var(--fold-color-text-muted);font-family:inherit;font-size:10px;font-weight:600;cursor:pointer;text-align:center;text-decoration:none;transition:color .1s ease,background .1s ease,border-color .1s ease;white-space:nowrap}.tab-bar-item:hover{color:var(--fold-color-text-secondary)}.tab-bar-item.is-disabled{opacity:.5;cursor:default;pointer-events:none}.size-comfortable .tab-bar-item{flex:0 1 auto;gap:var(--fold-space-sm);padding:var(--fold-space-md) var(--fold-space-lg);font-size:var(--fold-text-sm)}.dir-vertical.size-comfortable .tab-bar-item{padding:var(--fold-space-md) var(--fold-space-md);font-size:var(--fold-text-md)}.style-underline{border-bottom:1px solid var(--fold-color-border)}.style-underline .tab-bar-item{border-bottom:2px solid transparent;margin-bottom:-1px}.style-underline .tab-bar-item.is-active{color:var(--fold-color-text);border-bottom-color:var(--fold-color-primary)}.style-fill .tab-bar-item{border-radius:var(--fold-radius-sm);border:1px solid transparent}.style-fill .tab-bar-item.is-active{background:var(--fold-color-primary-surface);color:var(--fold-color-primary-text);font-weight:600}.style-fill .tab-bar-item:hover:not(.is-active){background:var(--fold-color-surface-hover);color:var(--fold-color-text)}.dir-vertical{flex-direction:column}.dir-vertical .tab-bar-item{flex:none;justify-content:flex-start;text-align:left;padding:var(--fold-space-sm) var(--fold-space-md);gap:var(--fold-space-sm);border-radius:var(--fold-radius-sm);font-size:var(--fold-text-sm);font-weight:500}.dir-vertical.style-underline{border-bottom:none;border-right:1px solid var(--fold-color-border)}.dir-vertical.style-underline .tab-bar-item{border-bottom:none;border-left:2px solid transparent;margin-bottom:0;margin-right:-1px}.dir-vertical.style-underline .tab-bar-item.is-active{border-left-color:var(--fold-color-primary)}.dir-vertical.style-fill .tab-bar-item.is-active{background:var(--fold-color-primary-surface);color:var(--fold-color-primary-text);font-weight:600}.is-collapsed .tab-bar-item{position:relative;flex:0 0 auto;gap:0}.is-collapsed .tab-bar-item:not(.is-active) .tab-bar-badge{display:none}.is-collapsed .tab-bar-item.is-active{flex:1 1 auto;min-width:0;gap:var(--fold-space-sm)}.is-collapsed .tab-bar-item.is-active .tab-bar-label{overflow:hidden;text-overflow:ellipsis}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active) .tab-bar-icon~.tab-bar-label{position:absolute;padding:var(--fold-space-xs) var(--fold-space-sm);border-radius:var(--fold-radius-sm);background:var(--fold-color-glass);border:1px solid var(--fold-color-glass-border);color:var(--fold-color-text);font-size:var(--fold-text-xs);font-weight:500;white-space:nowrap;opacity:0;pointer-events:none;box-shadow:var(--fold-shadow-md);transition:opacity var(--fold-motion-fast);z-index:100}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-icon~.tab-bar-label{left:calc(100% + 8px);top:50%;transform:translateY(-50%)}.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active) .tab-bar-icon~.tab-bar-label{top:calc(100% + 6px);left:50%;transform:translate(-50%)}.dir-vertical.is-collapsed .tab-bar-item:hover .tab-bar-icon~.tab-bar-label,.dir-vertical.is-collapsed .tab-bar-item:focus-visible .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active):hover .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active):focus-visible .tab-bar-icon~.tab-bar-label{opacity:1}.dir-vertical.is-collapsed{align-items:center}.dir-vertical.is-collapsed .tab-bar-item,.dir-vertical.is-collapsed .tab-bar-item.is-active{justify-content:center;gap:0}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-badge{display:block;position:absolute;top:-2px;right:-2px;transform:scale(.85);transform-origin:top right;pointer-events:none}:host:has(.is-collapsed){overflow:visible}.tab-bar-icon{flex-shrink:0}.tab-bar-initial{display:grid;place-items:center;width:18px;height:18px;border-radius:var(--fold-radius-sm);font-size:11px;font-weight:700;line-height:1;text-transform:uppercase}.tab-bar-badge{flex-shrink:0;font-variant-numeric:tabular-nums}.tab-bar-item:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px;border-radius:var(--fold-radius-sm)}:host(.is-standalone.is-horizontal){margin-block-end:var(--fold-nav-layout-gap, var(--fold-space-lg, 16px))}\n"] }]
7895
10195
  }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], activeKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeKey", required: false }] }, { type: i0.Output, args: ["activeKeyChange"] }], activeStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeStyle", required: false }] }], direction: [{ type: i0.Input, args: [{ isSignal: true, alias: "direction", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], background: [{ type: i0.Input, args: [{ isSignal: true, alias: "background", required: false }] }] } });
7896
10196
 
7897
10197
  /**
@@ -9025,6 +11325,528 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
9025
11325
  args: [{ selector: "fold-dropdown", standalone: true, imports: [FoldPopoverComponent], template: "<fold-popover\n [placement]=\"placement()\"\n [offset]=\"offset()\"\n [autoFocus]=\"false\"\n [(open)]=\"open\"\n>\n <ng-content select=\"[foldPopoverTrigger]\" />\n <div\n foldPopoverPanel\n class=\"fdrop-menu\"\n role=\"menu\"\n [attr.aria-label]=\"ariaLabel() ?? null\"\n (keydown)=\"onMenuKeydown($event)\"\n (click)=\"onMenuActivate($event)\"\n >\n <ng-content />\n </div>\n</fold-popover>\n", styles: [":host{display:contents}.fdrop-menu{display:flex;flex-direction:column;gap:1px;min-width:11rem;padding:var(--fold-space-xs)}\n"] }]
9026
11326
  }], ctorParameters: () => [], propDecorators: { placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], offset: [{ type: i0.Input, args: [{ isSignal: true, alias: "offset", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], items: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => FoldDropdownItemComponent), { isSignal: true }] }] } });
9027
11327
 
11328
+ /**
11329
+ * `date` moved by `delta` periods **in the units the view reads in** — a month
11330
+ * at a time in the month view, a week in the week view, a day in the day view.
11331
+ * The list view follows the month, since that is the window it lists.
11332
+ *
11333
+ * An unrecognised view pages by month, the safest of the four to be wrong
11334
+ * about: it always lands on a real date the other views can also show.
11335
+ */
11336
+ function foldShiftDate(view, date, delta, weekStartsOn = "mon") {
11337
+ if (view === "day") {
11338
+ return foldAddDays(date, delta);
11339
+ }
11340
+ if (view === "week") {
11341
+ return foldAddDays(foldStartOfWeek(date, weekStartsOn), delta * 7);
11342
+ }
11343
+ return foldAddMonths(foldStartOfMonth(date), delta);
11344
+ }
11345
+ /**
11346
+ * The days `view` covers around `date` — what a caller passes to its data
11347
+ * source, and what the list view scopes itself to.
11348
+ *
11349
+ * The **month** view's range is wider than its month: the grid paints the days
11350
+ * either side of it, and they carry their events too. The **list** view's is
11351
+ * not, because a list has no padding days — an April row in a May list is an
11352
+ * event the reader did not ask for.
11353
+ */
11354
+ function foldRangeForView(view, date, weekStartsOn = "mon") {
11355
+ if (view === "day") {
11356
+ return { from: date, to: date };
11357
+ }
11358
+ if (view === "week") {
11359
+ const start = foldStartOfWeek(date, weekStartsOn);
11360
+ return { from: start, to: foldAddDays(start, 6) };
11361
+ }
11362
+ if (view === "list") {
11363
+ return { from: foldStartOfMonth(date), to: foldEndOfMonth(date) };
11364
+ }
11365
+ const first = foldStartOfWeek(foldStartOfMonth(date), weekStartsOn);
11366
+ const last = foldAddDays(foldStartOfWeek(foldEndOfMonth(date), weekStartsOn), 6);
11367
+ return { from: first, to: last };
11368
+ }
11369
+ /**
11370
+ * The heading for the period on display, localised through `Intl` — "May 2026",
11371
+ * "18–24 May 2026", "Monday 18 May 2026". A week spanning two months or two
11372
+ * years names both ends, which `Intl`'s range formatter handles per locale.
11373
+ */
11374
+ function foldViewTitle(view, date, locale, weekStartsOn = "mon") {
11375
+ if (view === "day") {
11376
+ return foldCalendarFormatter(locale, "dateFullWeekday").format(foldToNativeDate(date));
11377
+ }
11378
+ if (view === "week") {
11379
+ const start = foldStartOfWeek(date, weekStartsOn);
11380
+ return foldFormatRange(start, foldAddDays(start, 6), locale, "dayMonthYear");
11381
+ }
11382
+ return foldCalendarFormatter(locale, "monthYear").format(foldToNativeDate(date));
11383
+ }
11384
+
11385
+ /**
11386
+ * `<fold-calendar-toolbar>` — the chrome above a calendar: jump to today, page
11387
+ * back and forward, the period's name, and the view switch.
11388
+ *
11389
+ * It owns no data. Both pieces of state it touches are two-way `model`s, so
11390
+ * the page binds the same `date` and `view` it hands to whichever view is on
11391
+ * screen, and paging keeps working without a single output handler.
11392
+ *
11393
+ * The step matches the reading: a month at a time under the month view, a week
11394
+ * under the week view, a day under the day view — see `foldShiftDate`.
11395
+ *
11396
+ * ## Inputs
11397
+ * | Input | Type | Default | Meaning |
11398
+ * |----------------|--------------------------|---------|---------|
11399
+ * | `date` | `FoldCalendarDate` | — | **Two-way.** The period on display. |
11400
+ * | `view` | `FoldCalendarView` | `'month'` | **Two-way.** Which reading is on screen. |
11401
+ * | `views` | `(FoldCalendarView \| FoldCalendarViewOption)[]` | all four | Which switches to offer; pass fewer to drop one, or `{ value, label }` to add your own. |
11402
+ * | `today` | `FoldCalendarDate` | local clock | Where the "today" button jumps to. |
11403
+ * | `weekStartsOn` | `FoldWeekday` | the locale's | How the week view snaps and names itself. |
11404
+ * | `locale` | `string` | runtime | Drives the title through `Intl`. |
11405
+ * | `labels` | `Partial<FoldCalendarLabels>` | — | Per-instance label overrides. |
11406
+ *
11407
+ * ## Slots & templates
11408
+ * | Selector | Region |
11409
+ * |------------------------|--------|
11410
+ * | `[actions]` | Trailing edge — the page's own buttons ("New event", filters). |
11411
+ * | `ng-template[foldCalendarTitle]` | Replaces the `<h2>` title — reword it, or give it the heading level the page's outline needs. |
11412
+ *
11413
+ * ## Theming
11414
+ * | CSS variable | Default | Sets |
11415
+ * |------------------------------------|---------|------|
11416
+ * | `--fold-calendar-toolbar-step-size`| `28px` | The back/forward buttons. |
11417
+ *
11418
+ * ## Extending
11419
+ * `views` accepts an app's own reading — `[views]="['month', {value: 'rooms', label: 'Rooms'}]"`.
11420
+ * Paging and titling an unrecognised view fall back to month semantics, so a
11421
+ * custom view still lands on real dates.
11422
+ *
11423
+ * ## Accessibility
11424
+ * The title is the toolbar's live heading: it carries `aria-live="polite"`, so
11425
+ * paging announces the period a keyboard user just moved to instead of leaving
11426
+ * the change silent.
11427
+ *
11428
+ * @selector `fold-calendar-toolbar`
11429
+ *
11430
+ * @example
11431
+ * ```html
11432
+ * <fold-calendar-toolbar [(date)]="date" [(view)]="view" [today]="today">
11433
+ * <button actions foldButton (click)="create()">New event</button>
11434
+ * </fold-calendar-toolbar>
11435
+ * ```
11436
+ */
11437
+ class FoldCalendarToolbarComponent {
11438
+ /** The period on display. */
11439
+ date = model.required(/* @ts-ignore */
11440
+ ...(ngDevMode ? [{ debugName: "date" }] : /* istanbul ignore next */ []));
11441
+ /** Which reading is on screen. @default 'month' */
11442
+ view = model("month", /* @ts-ignore */
11443
+ ...(ngDevMode ? [{ debugName: "view" }] : /* istanbul ignore next */ []));
11444
+ /**
11445
+ * Which switches to offer — a built-in name, or `{ value, label }` for a view
11446
+ * the app owns. @default every built-in view
11447
+ */
11448
+ views = input(["month", "week", "day", "list"], /* @ts-ignore */
11449
+ ...(ngDevMode ? [{ debugName: "views" }] : /* istanbul ignore next */ []));
11450
+ /** The day the "today" button jumps to; defaults to the local clock. */
11451
+ today = input(/* @ts-ignore */
11452
+ ...(ngDevMode ? [undefined, { debugName: "today" }] : /* istanbul ignore next */ []));
11453
+ /** Day the week starts on. @default the locale's own first day */
11454
+ weekStartsOn = input(/* @ts-ignore */
11455
+ ...(ngDevMode ? [undefined, { debugName: "weekStartsOn" }] : /* istanbul ignore next */ []));
11456
+ /** Labels and locale — see the directive. */
11457
+ chrome = inject(FoldCalendarChromeDirective);
11458
+ /** The anchor in force: the caller's, else the one the locale declares. */
11459
+ anchor = computed(() => this.chrome.anchor(this.weekStartsOn()), /* @ts-ignore */
11460
+ ...(ngDevMode ? [{ debugName: "anchor" }] : /* istanbul ignore next */ []));
11461
+ projectedTitle = contentChild(FoldCalendarTitleDirective, /* @ts-ignore */
11462
+ ...(ngDevMode ? [{ debugName: "projectedTitle" }] : /* istanbul ignore next */ []));
11463
+ /** The custom title template, when one is projected. */
11464
+ titleContent = computed(() => this.projectedTitle()?.template ?? null, /* @ts-ignore */
11465
+ ...(ngDevMode ? [{ debugName: "titleContent" }] : /* istanbul ignore next */ []));
11466
+ /** The period's name — "May 2026", "18–24 May 2026", "Wednesday 20 May 2026". */
11467
+ title = computed(() => foldViewTitle(this.view(), this.date(), this.chrome.locale(), this.anchor()), /* @ts-ignore */
11468
+ ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
11469
+ /** The period the title stands for — handed to a projected title template. */
11470
+ range = computed(() => foldRangeForView(this.view(), this.date(), this.anchor()), /* @ts-ignore */
11471
+ ...(ngDevMode ? [{ debugName: "range" }] : /* istanbul ignore next */ []));
11472
+ options = computed(() => {
11473
+ const labels = this.chrome.l();
11474
+ const named = {
11475
+ month: labels.viewMonth,
11476
+ week: labels.viewWeek,
11477
+ day: labels.viewDay,
11478
+ list: labels.viewList,
11479
+ };
11480
+ return this.views().map((view) => typeof view === "string"
11481
+ ? { value: view, label: named[view] ?? view }
11482
+ : { value: view.value, label: view.label });
11483
+ }, /* @ts-ignore */
11484
+ ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
11485
+ /** Pages by one period in the units the current view reads in. */
11486
+ shift(delta) {
11487
+ this.date.set(foldShiftDate(this.view(), this.date(), delta, this.anchor()));
11488
+ }
11489
+ goToday() {
11490
+ this.date.set(this.today() ?? foldToday());
11491
+ }
11492
+ /** The switch emits a bare `string`; only a view it actually offers is kept. */
11493
+ onViewChange(value) {
11494
+ const offered = this.options().some((option) => option.value === value);
11495
+ if (offered) {
11496
+ this.view.set(value);
11497
+ }
11498
+ }
11499
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCalendarToolbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11500
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldCalendarToolbarComponent, isStandalone: true, selector: "fold-calendar-toolbar", inputs: { date: { classPropertyName: "date", publicName: "date", isSignal: true, isRequired: true, transformFunction: null }, view: { classPropertyName: "view", publicName: "view", isSignal: true, isRequired: false, transformFunction: null }, views: { classPropertyName: "views", publicName: "views", isSignal: true, isRequired: false, transformFunction: null }, today: { classPropertyName: "today", publicName: "today", isSignal: true, isRequired: false, transformFunction: null }, weekStartsOn: { classPropertyName: "weekStartsOn", publicName: "weekStartsOn", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { date: "dateChange", view: "viewChange" }, queries: [{ propertyName: "projectedTitle", first: true, predicate: FoldCalendarTitleDirective, descendants: true, isSignal: true }], hostDirectives: [{ directive: FoldCalendarChromeDirective, inputs: ["locale", "locale", "labels", "labels", "formats", "formats"] }], ngImport: i0, template: "<div class=\"foldcalt\">\n <div class=\"foldcalt-lead\">\n <button type=\"button\" class=\"foldcalt-today\" (click)=\"goToday()\">\n {{ chrome.l().todayAction }}\n </button>\n\n <div class=\"foldcalt-steps\">\n <button\n type=\"button\"\n class=\"foldcalt-step\"\n [attr.aria-label]=\"chrome.l().previousPeriod\"\n (click)=\"shift(-1)\"\n >\n <fold-icon name=\"chevron-left\" size=\"sm\" />\n </button>\n <button\n type=\"button\"\n class=\"foldcalt-step\"\n [attr.aria-label]=\"chrome.l().nextPeriod\"\n (click)=\"shift(1)\"\n >\n <fold-icon name=\"chevron-right\" size=\"sm\" />\n </button>\n </div>\n\n @if (titleContent(); as projected) {\n <div class=\"foldcalt-title\" aria-live=\"polite\">\n <ng-container\n [ngTemplateOutlet]=\"projected\"\n [ngTemplateOutletContext]=\"{\n $implicit: title(),\n from: range().from,\n to: range().to,\n view: view(),\n }\"\n />\n </div>\n } @else {\n <h2 class=\"foldcalt-title\" aria-live=\"polite\">{{ title() }}</h2>\n }\n </div>\n\n <div class=\"foldcalt-trail\">\n @if (options().length > 1) {\n <fold-view-toggle\n size=\"sm\"\n [ariaLabel]=\"chrome.l().viewSwitch\"\n [options]=\"options()\"\n [value]=\"view()\"\n (valueChange)=\"onViewChange($event)\"\n />\n }\n <ng-content select=\"[actions]\" />\n </div>\n</div>\n", styles: [":host{display:block;container-type:inline-size}.foldcalt{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--fold-space-md)}.foldcalt-lead,.foldcalt-trail{display:flex;align-items:center;gap:var(--fold-space-sm);min-width:0}.foldcalt-today{padding:var(--fold-space-xs) var(--fold-space-md);border:1px solid var(--fold-color-border);border-radius:var(--fold-radius-sm);background:var(--fold-color-surface-raised);color:var(--fold-color-text);font:inherit;font-size:var(--fold-text-sm);font-weight:500;cursor:pointer;white-space:nowrap;transition:background-color var(--fold-motion-fast, .12s) ease}.foldcalt-today:hover{background:var(--fold-color-surface-hover)}.foldcalt-today:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:2px}.foldcalt-steps{display:inline-flex;gap:1px}.foldcalt-step{display:inline-flex;align-items:center;justify-content:center;width:var(--fold-calendar-toolbar-step-size, 28px);height:var(--fold-calendar-toolbar-step-size, 28px);padding:0;border:1px solid var(--fold-color-border);border-radius:var(--fold-radius-sm);background:var(--fold-color-surface-raised);color:var(--fold-color-text-secondary);cursor:pointer;transition:background-color var(--fold-motion-fast, .12s) ease,color var(--fold-motion-fast, .12s) ease}.foldcalt-step:hover{background:var(--fold-color-surface-hover);color:var(--fold-color-text)}.foldcalt-step:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:2px}.foldcalt-title{margin:0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--fold-color-text);font-size:var(--fold-text-lg);font-weight:700;letter-spacing:-.01em}@container (max-width: 560px){.foldcalt-lead{flex-wrap:wrap}.foldcalt-title{order:-1;flex:1 0 100%}}@media(forced-colors:active){.foldcalt-today:focus-visible,.foldcalt-step:focus-visible{outline-color:Highlight}}@media(prefers-reduced-motion:reduce){.foldcalt-today,.foldcalt-step{transition:none}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: FoldIconComponent, selector: "fold-icon", inputs: ["name", "size", "tone", "title"] }, { kind: "component", type: FoldViewToggleComponent, selector: "fold-view-toggle", inputs: ["options", "value", "ariaLabel", "size", "activeStyle", "iconOnly"], outputs: ["valueChange"] }] });
11501
+ }
11502
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCalendarToolbarComponent, decorators: [{
11503
+ type: Component,
11504
+ args: [{ selector: "fold-calendar-toolbar", standalone: true, imports: [NgTemplateOutlet, FoldIconComponent, FoldViewToggleComponent], hostDirectives: [
11505
+ {
11506
+ directive: FoldCalendarChromeDirective,
11507
+ inputs: ["locale", "labels", "formats"],
11508
+ },
11509
+ ], template: "<div class=\"foldcalt\">\n <div class=\"foldcalt-lead\">\n <button type=\"button\" class=\"foldcalt-today\" (click)=\"goToday()\">\n {{ chrome.l().todayAction }}\n </button>\n\n <div class=\"foldcalt-steps\">\n <button\n type=\"button\"\n class=\"foldcalt-step\"\n [attr.aria-label]=\"chrome.l().previousPeriod\"\n (click)=\"shift(-1)\"\n >\n <fold-icon name=\"chevron-left\" size=\"sm\" />\n </button>\n <button\n type=\"button\"\n class=\"foldcalt-step\"\n [attr.aria-label]=\"chrome.l().nextPeriod\"\n (click)=\"shift(1)\"\n >\n <fold-icon name=\"chevron-right\" size=\"sm\" />\n </button>\n </div>\n\n @if (titleContent(); as projected) {\n <div class=\"foldcalt-title\" aria-live=\"polite\">\n <ng-container\n [ngTemplateOutlet]=\"projected\"\n [ngTemplateOutletContext]=\"{\n $implicit: title(),\n from: range().from,\n to: range().to,\n view: view(),\n }\"\n />\n </div>\n } @else {\n <h2 class=\"foldcalt-title\" aria-live=\"polite\">{{ title() }}</h2>\n }\n </div>\n\n <div class=\"foldcalt-trail\">\n @if (options().length > 1) {\n <fold-view-toggle\n size=\"sm\"\n [ariaLabel]=\"chrome.l().viewSwitch\"\n [options]=\"options()\"\n [value]=\"view()\"\n (valueChange)=\"onViewChange($event)\"\n />\n }\n <ng-content select=\"[actions]\" />\n </div>\n</div>\n", styles: [":host{display:block;container-type:inline-size}.foldcalt{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--fold-space-md)}.foldcalt-lead,.foldcalt-trail{display:flex;align-items:center;gap:var(--fold-space-sm);min-width:0}.foldcalt-today{padding:var(--fold-space-xs) var(--fold-space-md);border:1px solid var(--fold-color-border);border-radius:var(--fold-radius-sm);background:var(--fold-color-surface-raised);color:var(--fold-color-text);font:inherit;font-size:var(--fold-text-sm);font-weight:500;cursor:pointer;white-space:nowrap;transition:background-color var(--fold-motion-fast, .12s) ease}.foldcalt-today:hover{background:var(--fold-color-surface-hover)}.foldcalt-today:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:2px}.foldcalt-steps{display:inline-flex;gap:1px}.foldcalt-step{display:inline-flex;align-items:center;justify-content:center;width:var(--fold-calendar-toolbar-step-size, 28px);height:var(--fold-calendar-toolbar-step-size, 28px);padding:0;border:1px solid var(--fold-color-border);border-radius:var(--fold-radius-sm);background:var(--fold-color-surface-raised);color:var(--fold-color-text-secondary);cursor:pointer;transition:background-color var(--fold-motion-fast, .12s) ease,color var(--fold-motion-fast, .12s) ease}.foldcalt-step:hover{background:var(--fold-color-surface-hover);color:var(--fold-color-text)}.foldcalt-step:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:2px}.foldcalt-title{margin:0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--fold-color-text);font-size:var(--fold-text-lg);font-weight:700;letter-spacing:-.01em}@container (max-width: 560px){.foldcalt-lead{flex-wrap:wrap}.foldcalt-title{order:-1;flex:1 0 100%}}@media(forced-colors:active){.foldcalt-today:focus-visible,.foldcalt-step:focus-visible{outline-color:Highlight}}@media(prefers-reduced-motion:reduce){.foldcalt-today,.foldcalt-step{transition:none}}\n"] }]
11510
+ }], propDecorators: { date: [{ type: i0.Input, args: [{ isSignal: true, alias: "date", required: true }] }, { type: i0.Output, args: ["dateChange"] }], view: [{ type: i0.Input, args: [{ isSignal: true, alias: "view", required: false }] }, { type: i0.Output, args: ["viewChange"] }], views: [{ type: i0.Input, args: [{ isSignal: true, alias: "views", required: false }] }], today: [{ type: i0.Input, args: [{ isSignal: true, alias: "today", required: false }] }], weekStartsOn: [{ type: i0.Input, args: [{ isSignal: true, alias: "weekStartsOn", required: false }] }], projectedTitle: [{ type: i0.ContentChild, args: [i0.forwardRef(() => FoldCalendarTitleDirective), { isSignal: true }] }] } });
11511
+
11512
+ /** Days the agenda returns when the caller does not say. */
11513
+ const DEFAULT_LIMIT = 8;
11514
+ /**
11515
+ * Whether an event is asking for something.
11516
+ *
11517
+ * `warning` and `alert` are the two tones that mean "attention" in this
11518
+ * system, so they are what `todo` keeps — the agenda reads the same semantic
11519
+ * scale the chips paint with, rather than inventing a second one. Pass
11520
+ * `isActionable` to define it otherwise.
11521
+ */
11522
+ function foldIsActionable(event) {
11523
+ return event.tone === "warning" || event.tone === "alert";
11524
+ }
11525
+ /** At least one day, and never `NaN` — `slice(0, NaN)` returns nothing at all. */
11526
+ function clampLimit(limit) {
11527
+ if (limit === undefined || !Number.isFinite(limit)) {
11528
+ return DEFAULT_LIMIT;
11529
+ }
11530
+ return Math.max(1, Math.trunc(limit));
11531
+ }
11532
+ /**
11533
+ * Events still ahead of `from`, grouped by day and capped to `limit` days.
11534
+ *
11535
+ * An event already running is grouped under `from` rather than its real start:
11536
+ * a three-week absence that began last Monday belongs at the top of "what's
11537
+ * next", not buried in a past day the rail never shows.
11538
+ */
11539
+ function foldBuildAgenda(events, options) {
11540
+ const { from } = options;
11541
+ const mode = options.mode ?? "all";
11542
+ const limit = clampLimit(options.limit);
11543
+ const actionable = options.isActionable ?? foldIsActionable;
11544
+ const ahead = events
11545
+ .filter((event) => event.end >= from)
11546
+ .filter((event) => mode !== "todo" || actionable(event));
11547
+ const ordered = [...ahead].sort((a, b) => a.start < b.start ? -1 : a.start > b.start ? 1 : 0);
11548
+ const byDay = new Map();
11549
+ for (const event of ordered) {
11550
+ const key = event.start < from ? from : event.start;
11551
+ const bucket = byDay.get(key);
11552
+ if (bucket === undefined) {
11553
+ byDay.set(key, [event]);
11554
+ continue;
11555
+ }
11556
+ bucket.push(event);
11557
+ }
11558
+ const days = [...byDay.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
11559
+ return {
11560
+ groups: days
11561
+ .slice(0, limit)
11562
+ .map(([date, group]) => ({ date, events: group })),
11563
+ truncated: Math.max(0, days.length - limit),
11564
+ };
11565
+ }
11566
+ /**
11567
+ * How many events are asking for attention from `from` on — the rail's badge.
11568
+ *
11569
+ * Takes the same `isActionable` the agenda does, so a badge can never disagree
11570
+ * with the list under it.
11571
+ */
11572
+ function foldCountActionable(events, from, isActionable = foldIsActionable) {
11573
+ return events.filter((event) => event.end >= from && isActionable(event))
11574
+ .length;
11575
+ }
11576
+
11577
+ /** Days ahead within which a weekday name still reads faster than a date. */
11578
+ const NEAR_HORIZON = 7;
11579
+ /**
11580
+ * `<fold-calendar-agenda>` — a rail of what is still ahead, grouped by day.
11581
+ *
11582
+ * The counterpart to the grids: they answer "what does this month look like",
11583
+ * this answers "what do I do next". It carries a **`todo` slice** that keeps
11584
+ * only the events asking for attention (the `warning` and `alert` tones — the
11585
+ * same scale the chips paint with), so the rail doubles as a work queue with a
11586
+ * count.
11587
+ *
11588
+ * ## Inputs
11589
+ * | Input | Type | Default | Meaning |
11590
+ * |-------------|--------------------------|---------|---------|
11591
+ * | `from` | `FoldCalendarDate` | — | Where "ahead" starts; anything ending before it is past. |
11592
+ * | `events` | `FoldCalendarEvent<T>[]` | `[]` | Source events. |
11593
+ * | `mode` | `'todo' \| 'all'` | `'todo'` | **Two-way.** Which slice is shown. |
11594
+ * | `collapsed` | `boolean` | `false` | **Two-way.** Collapsed to a spine — persist it if you want it to stick. |
11595
+ * | `limit` | `number` | `8` | Days to show at most; what it cuts off is announced, not swallowed. |
11596
+ * | `isActionable` | `(event) => boolean` | tones | What the `todo` slice and the badge both count. |
11597
+ * | `locale` | `string` | runtime | Drives the day names through `Intl`. |
11598
+ * | `labels` | `Partial<FoldCalendarLabels>` | — | Per-instance label overrides. |
11599
+ *
11600
+ * ## Outputs
11601
+ * | Output | Payload | Fires on |
11602
+ * |--------------|------------------------|----------|
11603
+ * | `dayClick` | `FoldCalendarDate` | A day heading activated. |
11604
+ * | `eventClick` | `FoldCalendarEvent<T>` | A row activated. |
11605
+ *
11606
+ * ## Templates
11607
+ * | Directive | Replaces |
11608
+ * |-----------------------|----------|
11609
+ * | `foldCalendarEvent` | The inside of a row. |
11610
+ * | `foldCalendarHeading` | The day heading — where a per-day count or action goes. |
11611
+ *
11612
+ * ## Theming
11613
+ * | CSS variable | Default | Sets |
11614
+ * |-----------------------------------------|---------|------|
11615
+ * | `--fold-calendar-agenda-width` | `320px` | Width of the expanded rail. |
11616
+ * | `--fold-calendar-agenda-height` | `100%` | Height — match a grid beside it, or pin it. |
11617
+ * | `--fold-calendar-agenda-spine-width` | `44px` | Width once collapsed. |
11618
+ * | `--fold-calendar-agenda-badge-size` | `20px` | The count badge. |
11619
+ * | `--fold-calendar-agenda-control-size` | `26px` | The collapse control. |
11620
+ *
11621
+ * A day within the next week is named relatively — "Today", "Tomorrow", then
11622
+ * its weekday — because that reads faster than a date at this distance.
11623
+ *
11624
+ * @selector `fold-calendar-agenda`
11625
+ *
11626
+ * @example
11627
+ * ```html
11628
+ * <fold-calendar-agenda
11629
+ * [from]="today"
11630
+ * [events]="events()"
11631
+ * [(mode)]="agendaMode"
11632
+ * [(collapsed)]="agendaCollapsed"
11633
+ * (eventClick)="open($event)"
11634
+ * />
11635
+ * ```
11636
+ */
11637
+ class FoldCalendarAgendaComponent {
11638
+ /** The day "ahead" starts from. */
11639
+ from = input.required(/* @ts-ignore */
11640
+ ...(ngDevMode ? [{ debugName: "from" }] : /* istanbul ignore next */ []));
11641
+ /** Source events. */
11642
+ events = input([], /* @ts-ignore */
11643
+ ...(ngDevMode ? [{ debugName: "events" }] : /* istanbul ignore next */ []));
11644
+ /** Which slice is shown. @default 'todo' */
11645
+ mode = model("todo", /* @ts-ignore */
11646
+ ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
11647
+ /** Collapsed to a spine. @default false */
11648
+ collapsed = model(false, /* @ts-ignore */
11649
+ ...(ngDevMode ? [{ debugName: "collapsed" }] : /* istanbul ignore next */ []));
11650
+ /** Days to show at most. @default 8 */
11651
+ limit = input(8, { ...(ngDevMode ? { debugName: "limit" } : /* istanbul ignore next */ {}), transform: numberAttribute });
11652
+ /**
11653
+ * What counts as asking for attention — the `todo` slice **and** the badge,
11654
+ * from one definition, so the two can never disagree.
11655
+ * @default the `warning` and `alert` tones
11656
+ */
11657
+ isActionable = input(foldIsActionable, /* @ts-ignore */
11658
+ ...(ngDevMode ? [{ debugName: "isActionable" }] : /* istanbul ignore next */ []));
11659
+ /** A day heading was activated. */
11660
+ dayClick = output();
11661
+ /** A row was activated. */
11662
+ eventClick = output();
11663
+ /** Labels, locale and the projected chip template — see the directive. */
11664
+ chrome = inject(FoldCalendarChromeDirective);
11665
+ projectedHeading = contentChild(FoldCalendarHeadingDirective, /* @ts-ignore */
11666
+ ...(ngDevMode ? [{ debugName: "projectedHeading" }] : /* istanbul ignore next */ []));
11667
+ /** The custom day-heading template, when one is projected. */
11668
+ headingContent = computed(() => this.projectedHeading()?.template ?? null, /* @ts-ignore */
11669
+ ...(ngDevMode ? [{ debugName: "headingContent" }] : /* istanbul ignore next */ []));
11670
+ agenda = computed(() => foldBuildAgenda(this.events(), {
11671
+ from: this.from(),
11672
+ mode: this.mode(),
11673
+ limit: this.limit(),
11674
+ isActionable: this.isActionable(),
11675
+ }), /* @ts-ignore */
11676
+ ...(ngDevMode ? [{ debugName: "agenda" }] : /* istanbul ignore next */ []));
11677
+ groups = computed(() => this.agenda().groups, /* @ts-ignore */
11678
+ ...(ngDevMode ? [{ debugName: "groups" }] : /* istanbul ignore next */ []));
11679
+ /** Days `limit` cut off — "there is more" is not the same as "there is none". */
11680
+ truncated = computed(() => this.agenda().truncated, /* @ts-ignore */
11681
+ ...(ngDevMode ? [{ debugName: "truncated" }] : /* istanbul ignore next */ []));
11682
+ /** How much is asking for attention — the rail's badge, in either mode. */
11683
+ todoCount = computed(() => foldCountActionable(this.events(), this.from(), this.isActionable()), /* @ts-ignore */
11684
+ ...(ngDevMode ? [{ debugName: "todoCount" }] : /* istanbul ignore next */ []));
11685
+ /** The rail's own name, and the one the collapsed spine repeats. */
11686
+ title = computed(() => this.mode() === "todo"
11687
+ ? this.chrome.l().agendaTodo
11688
+ : this.chrome.l().agendaUpcoming, /* @ts-ignore */
11689
+ ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
11690
+ emptyMessage = computed(() => this.mode() === "todo"
11691
+ ? this.chrome.l().agendaEmptyTodo
11692
+ : this.chrome.l().agendaEmptyUpcoming, /* @ts-ignore */
11693
+ ...(ngDevMode ? [{ debugName: "emptyMessage" }] : /* istanbul ignore next */ []));
11694
+ /** Day of the month, for the rail's number. */
11695
+ dayNumber(group) {
11696
+ return group.date.slice(8, 10);
11697
+ }
11698
+ /**
11699
+ * "Today", "Tomorrow", then the weekday for the rest of the week — a name
11700
+ * beats a date while the day is still near.
11701
+ */
11702
+ relativeName(group) {
11703
+ const offset = foldDaysBetween(this.from(), group.date);
11704
+ if (offset === 0) {
11705
+ return this.chrome.l().relativeToday;
11706
+ }
11707
+ if (offset === 1) {
11708
+ return this.chrome.l().relativeTomorrow;
11709
+ }
11710
+ return this.chrome.format(group.date, "weekdayLong");
11711
+ }
11712
+ /** The month, shown only once the weekday alone stops being enough. */
11713
+ monthName(group) {
11714
+ if (foldDaysBetween(this.from(), group.date) < NEAR_HORIZON) {
11715
+ return "";
11716
+ }
11717
+ return this.chrome.format(group.date, "monthShort");
11718
+ }
11719
+ toggleMode(mode) {
11720
+ this.mode.set(mode);
11721
+ }
11722
+ toggleCollapsed() {
11723
+ this.collapsed.set(!this.collapsed());
11724
+ }
11725
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCalendarAgendaComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11726
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldCalendarAgendaComponent, isStandalone: true, selector: "fold-calendar-agenda", inputs: { from: { classPropertyName: "from", publicName: "from", isSignal: true, isRequired: true, transformFunction: null }, events: { classPropertyName: "events", publicName: "events", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, limit: { classPropertyName: "limit", publicName: "limit", isSignal: true, isRequired: false, transformFunction: null }, isActionable: { classPropertyName: "isActionable", publicName: "isActionable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { mode: "modeChange", collapsed: "collapsedChange", dayClick: "dayClick", eventClick: "eventClick" }, queries: [{ propertyName: "projectedHeading", first: true, predicate: FoldCalendarHeadingDirective, descendants: true, isSignal: true }], hostDirectives: [{ directive: FoldCalendarChromeDirective, inputs: ["locale", "locale", "labels", "labels", "formats", "formats"] }], ngImport: i0, template: "@if (collapsed()) {\n <aside class=\"foldcala is-collapsed\" [attr.aria-label]=\"chrome.l().agenda\">\n <button\n type=\"button\"\n class=\"foldcala-spine\"\n [attr.aria-expanded]=\"false\"\n [attr.aria-label]=\"chrome.l().expand\"\n (click)=\"toggleCollapsed()\"\n >\n <fold-icon name=\"inbox\" size=\"sm\" />\n @if (todoCount() > 0) {\n <span class=\"foldcala-spine-count\">{{ todoCount() }}</span>\n }\n <!-- The spine names what opening the rail will show, not always \"todo\". -->\n <span class=\"foldcala-spine-label\">{{ title() }}</span>\n </button>\n </aside>\n} @else {\n <aside class=\"foldcala\" [attr.aria-label]=\"chrome.l().agenda\">\n <header class=\"foldcala-head\">\n <span class=\"foldcala-title\">\n {{ title() }}\n @if (todoCount() > 0) {\n <span class=\"foldcala-count\">{{ todoCount() }}</span>\n }\n </span>\n\n <span class=\"foldcala-head-right\">\n <!-- Its own name: reusing the rail's would nest two regions under one. -->\n <span\n class=\"foldcala-modes\"\n role=\"group\"\n [attr.aria-label]=\"chrome.l().agendaModes\"\n >\n <button\n type=\"button\"\n [class.is-on]=\"mode() === 'todo'\"\n [attr.aria-pressed]=\"mode() === 'todo'\"\n (click)=\"toggleMode('todo')\"\n >\n {{ chrome.l().agendaTodo }}\n </button>\n <button\n type=\"button\"\n [class.is-on]=\"mode() === 'all'\"\n [attr.aria-pressed]=\"mode() === 'all'\"\n (click)=\"toggleMode('all')\"\n >\n {{ chrome.l().agendaUpcoming }}\n </button>\n </span>\n <button\n type=\"button\"\n class=\"foldcala-collapse\"\n [attr.aria-expanded]=\"true\"\n [attr.aria-label]=\"chrome.l().collapse\"\n (click)=\"toggleCollapsed()\"\n >\n <fold-icon name=\"chevron-right\" size=\"sm\" />\n </button>\n </span>\n </header>\n\n <div class=\"foldcala-body\">\n @if (groups().length === 0) {\n <p class=\"foldcala-empty\">{{ emptyMessage() }}</p>\n } @else {\n @for (group of groups(); track group.date) {\n <section class=\"foldcala-group\">\n <button\n type=\"button\"\n class=\"foldcala-date\"\n (click)=\"dayClick.emit(group.date)\"\n >\n @if (headingContent(); as projected) {\n <ng-container\n [ngTemplateOutlet]=\"projected\"\n [ngTemplateOutletContext]=\"{\n $implicit: group.date,\n events: group.events,\n }\"\n />\n } @else {\n <span class=\"foldcala-daynum\">{{ dayNumber(group) }}</span>\n <span class=\"foldcala-dmeta\">\n <span class=\"foldcala-dname\">{{ relativeName(group) }}</span>\n @if (monthName(group)) {\n <span class=\"foldcala-dmonth\">{{ monthName(group) }}</span>\n }\n </span>\n }\n </button>\n\n <ul class=\"foldcala-list\">\n @for (event of group.events; track event.id) {\n <li>\n <button\n type=\"button\"\n class=\"foldcal-chip\"\n [attr.data-tone]=\"event.tone ?? 'neutral'\"\n (click)=\"eventClick.emit(event)\"\n >\n @if (chrome.eventContent(); as projected) {\n <ng-container\n [ngTemplateOutlet]=\"projected\"\n [ngTemplateOutletContext]=\"{\n $implicit: event,\n band: null,\n }\"\n />\n } @else {\n <span class=\"foldcal-chip-bar\"></span>\n @if (event.icon; as icon) {\n <fold-icon\n class=\"foldcal-chip-icon\"\n [name]=\"icon\"\n size=\"sm\"\n />\n }\n <span class=\"foldcal-chip-body\">\n <span class=\"foldcal-chip-label\">{{\n event.label\n }}</span>\n @if (event.subline) {\n <span class=\"foldcal-chip-sub\">{{\n event.subline\n }}</span>\n }\n </span>\n }\n </button>\n </li>\n }\n </ul>\n </section>\n }\n\n <!-- `limit` stopped short of what is ahead: say so, rather than\n letting the rail look like the end of the story. -->\n @if (truncated(); as more) {\n <p class=\"foldcala-more\">{{ chrome.l().agendaMore(more) }}</p>\n }\n }\n </div>\n </aside>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block}.foldcal-chip{position:relative;display:flex;align-items:stretch;gap:var(--fold-space-sm);width:100%;padding:var(--fold-space-xs) var(--fold-space-sm);padding-inline-start:0;border:0;border-radius:var(--fold-calendar-band-radius, var(--fold-radius-sm));background:var(--fold-color-surface-raised);color:var(--fold-color-text);font:inherit;text-align:start;cursor:pointer;overflow:hidden;transition:background-color var(--fold-motion-fast) ease}.foldcal-chip:hover{background:var(--fold-color-surface-hover)}.foldcal-chip:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px}.foldcal-chip-bar{flex:0 0 var(--fold-calendar-bar-width, 3px);align-self:stretch;background:var(--fold-color-text-muted)}.foldcal-chip-icon{flex:none;align-self:center;margin-inline-start:var(--fold-space-sm);color:var(--fold-color-text-secondary)}.foldcal-chip-body{display:flex;flex-direction:column;gap:1px;min-width:0}.foldcal-chip-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--fold-text-sm);font-weight:500}.foldcal-chip-sub{color:var(--fold-color-text-secondary);font-size:var(--fold-text-xs)}.foldcal-chip[data-tone=success] .foldcal-chip-bar{background:var(--fold-color-success)}.foldcal-chip[data-tone=warning] .foldcal-chip-bar{background:var(--fold-color-warning)}.foldcal-chip[data-tone=alert] .foldcal-chip-bar{background:var(--fold-color-alert)}.foldcal-chip[data-tone=muted] .foldcal-chip-bar{background:var(--fold-color-text-faded)}.foldcal-chip[data-tone=muted] .foldcal-chip-label{color:var(--fold-color-text-secondary);text-decoration:line-through;text-decoration-thickness:1px}@media(forced-colors:active){.foldcal-chip{border:1px solid CanvasText}.foldcal-chip-bar{forced-color-adjust:none}}@media(prefers-reduced-motion:reduce){.foldcal-chip{transition:none}}.foldcala{display:flex;flex-direction:column;min-height:0;width:var(--fold-calendar-agenda-width, 320px);height:var(--fold-calendar-agenda-height, 100%);border:1px solid var(--fold-color-border);border-radius:var(--fold-radius-lg);background:var(--fold-color-surface-card);overflow:clip}.foldcala.is-collapsed{width:var(--fold-calendar-agenda-spine-width, 44px)}.foldcala-spine{flex:1;display:flex;flex-direction:column;align-items:center;gap:var(--fold-space-md);padding:var(--fold-space-md) 0;border:0;background:transparent;color:var(--fold-color-text-secondary);font:inherit;cursor:pointer}.foldcala-spine:hover{background:var(--fold-color-surface-hover);color:var(--fold-color-text)}.foldcala-spine:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px}.foldcala-spine-label{writing-mode:vertical-rl;font-size:var(--fold-text-xs);font-weight:600;letter-spacing:.06em;text-transform:uppercase}.foldcala-spine-count{display:inline-flex;align-items:center;justify-content:center;min-width:var(--fold-calendar-agenda-badge-size, 20px);padding:0 var(--fold-space-xs);border-radius:var(--fold-radius-pill);background:var(--fold-color-warning-surface);color:var(--fold-color-warning-text);font-size:var(--fold-text-xs);font-weight:700;font-variant-numeric:tabular-nums}.foldcala-head{display:flex;align-items:center;justify-content:space-between;gap:var(--fold-space-sm);padding:var(--fold-space-md);border-bottom:1px solid var(--fold-color-border-subtle)}.foldcala-title{display:inline-flex;align-items:center;gap:var(--fold-space-sm);color:var(--fold-color-text);font-size:var(--fold-text-sm);font-weight:700}.foldcala-count{display:inline-flex;align-items:center;justify-content:center;min-width:var(--fold-calendar-agenda-badge-size, 20px);padding:0 var(--fold-space-xs);border-radius:var(--fold-radius-pill);background:var(--fold-color-warning-surface);color:var(--fold-color-warning-text);font-size:var(--fold-text-xs);font-weight:700;font-variant-numeric:tabular-nums}.foldcala-head-right{display:inline-flex;align-items:center;gap:var(--fold-space-xs)}.foldcala-modes{display:inline-flex;padding:2px;border-radius:var(--fold-radius-sm);background:var(--fold-color-surface-sunken)}.foldcala-modes button{padding:2px var(--fold-space-sm);border:0;border-radius:var(--fold-radius-xs);background:transparent;color:var(--fold-color-text-secondary);font:inherit;font-size:var(--fold-text-xs);font-weight:500;cursor:pointer}.foldcala-modes button.is-on{background:var(--fold-color-surface-raised);color:var(--fold-color-text)}.foldcala-modes button:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:1px}.foldcala-collapse{display:inline-flex;align-items:center;justify-content:center;width:var(--fold-calendar-agenda-control-size, 26px);height:var(--fold-calendar-agenda-control-size, 26px);padding:0;border:1px solid var(--fold-color-border-subtle);border-radius:var(--fold-radius-sm);background:var(--fold-color-surface-subtle);color:var(--fold-color-text-secondary);cursor:pointer}.foldcala-collapse:hover{background:var(--fold-color-surface-hover);color:var(--fold-color-text)}.foldcala-collapse:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:1px}.foldcala-body{flex:1;min-height:0;overflow-y:auto;padding:var(--fold-space-sm)}.foldcala-group{display:grid;grid-template-columns:auto minmax(0,1fr);gap:var(--fold-space-sm);padding:var(--fold-space-sm) 0}.foldcala-group+.foldcala-group{border-top:1px solid var(--fold-color-border-subtle)}.foldcala-date{display:flex;align-items:baseline;gap:var(--fold-space-xs);padding:0;border:0;background:transparent;color:inherit;font:inherit;text-align:start;cursor:pointer}.foldcala-date:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:2px}.foldcala-daynum{color:var(--fold-color-text);font-size:var(--fold-text-lg);font-weight:700;font-variant-numeric:tabular-nums;line-height:1}.foldcala-dmeta{display:flex;flex-direction:column}.foldcala-dname{color:var(--fold-color-text-secondary);font-size:var(--fold-text-xs);font-weight:600;letter-spacing:.04em;text-transform:uppercase}.foldcala-dmonth{color:var(--fold-color-text-secondary);font-size:var(--fold-text-xs);text-transform:uppercase}.foldcala-list{display:flex;flex-direction:column;gap:var(--fold-space-xs);margin:0;padding:0;list-style:none}.foldcala-empty{margin:0;padding:var(--fold-space-xl) var(--fold-space-md);color:var(--fold-color-text-secondary);font-size:var(--fold-text-sm);text-align:center}@media(forced-colors:active){.foldcala-modes button.is-on{outline:1px solid Highlight}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: FoldIconComponent, selector: "fold-icon", inputs: ["name", "size", "tone", "title"] }] });
11727
+ }
11728
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCalendarAgendaComponent, decorators: [{
11729
+ type: Component,
11730
+ args: [{ selector: "fold-calendar-agenda", standalone: true, imports: [NgTemplateOutlet, FoldIconComponent], hostDirectives: [
11731
+ {
11732
+ directive: FoldCalendarChromeDirective,
11733
+ inputs: ["locale", "labels", "formats"],
11734
+ },
11735
+ ], template: "@if (collapsed()) {\n <aside class=\"foldcala is-collapsed\" [attr.aria-label]=\"chrome.l().agenda\">\n <button\n type=\"button\"\n class=\"foldcala-spine\"\n [attr.aria-expanded]=\"false\"\n [attr.aria-label]=\"chrome.l().expand\"\n (click)=\"toggleCollapsed()\"\n >\n <fold-icon name=\"inbox\" size=\"sm\" />\n @if (todoCount() > 0) {\n <span class=\"foldcala-spine-count\">{{ todoCount() }}</span>\n }\n <!-- The spine names what opening the rail will show, not always \"todo\". -->\n <span class=\"foldcala-spine-label\">{{ title() }}</span>\n </button>\n </aside>\n} @else {\n <aside class=\"foldcala\" [attr.aria-label]=\"chrome.l().agenda\">\n <header class=\"foldcala-head\">\n <span class=\"foldcala-title\">\n {{ title() }}\n @if (todoCount() > 0) {\n <span class=\"foldcala-count\">{{ todoCount() }}</span>\n }\n </span>\n\n <span class=\"foldcala-head-right\">\n <!-- Its own name: reusing the rail's would nest two regions under one. -->\n <span\n class=\"foldcala-modes\"\n role=\"group\"\n [attr.aria-label]=\"chrome.l().agendaModes\"\n >\n <button\n type=\"button\"\n [class.is-on]=\"mode() === 'todo'\"\n [attr.aria-pressed]=\"mode() === 'todo'\"\n (click)=\"toggleMode('todo')\"\n >\n {{ chrome.l().agendaTodo }}\n </button>\n <button\n type=\"button\"\n [class.is-on]=\"mode() === 'all'\"\n [attr.aria-pressed]=\"mode() === 'all'\"\n (click)=\"toggleMode('all')\"\n >\n {{ chrome.l().agendaUpcoming }}\n </button>\n </span>\n <button\n type=\"button\"\n class=\"foldcala-collapse\"\n [attr.aria-expanded]=\"true\"\n [attr.aria-label]=\"chrome.l().collapse\"\n (click)=\"toggleCollapsed()\"\n >\n <fold-icon name=\"chevron-right\" size=\"sm\" />\n </button>\n </span>\n </header>\n\n <div class=\"foldcala-body\">\n @if (groups().length === 0) {\n <p class=\"foldcala-empty\">{{ emptyMessage() }}</p>\n } @else {\n @for (group of groups(); track group.date) {\n <section class=\"foldcala-group\">\n <button\n type=\"button\"\n class=\"foldcala-date\"\n (click)=\"dayClick.emit(group.date)\"\n >\n @if (headingContent(); as projected) {\n <ng-container\n [ngTemplateOutlet]=\"projected\"\n [ngTemplateOutletContext]=\"{\n $implicit: group.date,\n events: group.events,\n }\"\n />\n } @else {\n <span class=\"foldcala-daynum\">{{ dayNumber(group) }}</span>\n <span class=\"foldcala-dmeta\">\n <span class=\"foldcala-dname\">{{ relativeName(group) }}</span>\n @if (monthName(group)) {\n <span class=\"foldcala-dmonth\">{{ monthName(group) }}</span>\n }\n </span>\n }\n </button>\n\n <ul class=\"foldcala-list\">\n @for (event of group.events; track event.id) {\n <li>\n <button\n type=\"button\"\n class=\"foldcal-chip\"\n [attr.data-tone]=\"event.tone ?? 'neutral'\"\n (click)=\"eventClick.emit(event)\"\n >\n @if (chrome.eventContent(); as projected) {\n <ng-container\n [ngTemplateOutlet]=\"projected\"\n [ngTemplateOutletContext]=\"{\n $implicit: event,\n band: null,\n }\"\n />\n } @else {\n <span class=\"foldcal-chip-bar\"></span>\n @if (event.icon; as icon) {\n <fold-icon\n class=\"foldcal-chip-icon\"\n [name]=\"icon\"\n size=\"sm\"\n />\n }\n <span class=\"foldcal-chip-body\">\n <span class=\"foldcal-chip-label\">{{\n event.label\n }}</span>\n @if (event.subline) {\n <span class=\"foldcal-chip-sub\">{{\n event.subline\n }}</span>\n }\n </span>\n }\n </button>\n </li>\n }\n </ul>\n </section>\n }\n\n <!-- `limit` stopped short of what is ahead: say so, rather than\n letting the rail look like the end of the story. -->\n @if (truncated(); as more) {\n <p class=\"foldcala-more\">{{ chrome.l().agendaMore(more) }}</p>\n }\n }\n </div>\n </aside>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block}.foldcal-chip{position:relative;display:flex;align-items:stretch;gap:var(--fold-space-sm);width:100%;padding:var(--fold-space-xs) var(--fold-space-sm);padding-inline-start:0;border:0;border-radius:var(--fold-calendar-band-radius, var(--fold-radius-sm));background:var(--fold-color-surface-raised);color:var(--fold-color-text);font:inherit;text-align:start;cursor:pointer;overflow:hidden;transition:background-color var(--fold-motion-fast) ease}.foldcal-chip:hover{background:var(--fold-color-surface-hover)}.foldcal-chip:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px}.foldcal-chip-bar{flex:0 0 var(--fold-calendar-bar-width, 3px);align-self:stretch;background:var(--fold-color-text-muted)}.foldcal-chip-icon{flex:none;align-self:center;margin-inline-start:var(--fold-space-sm);color:var(--fold-color-text-secondary)}.foldcal-chip-body{display:flex;flex-direction:column;gap:1px;min-width:0}.foldcal-chip-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--fold-text-sm);font-weight:500}.foldcal-chip-sub{color:var(--fold-color-text-secondary);font-size:var(--fold-text-xs)}.foldcal-chip[data-tone=success] .foldcal-chip-bar{background:var(--fold-color-success)}.foldcal-chip[data-tone=warning] .foldcal-chip-bar{background:var(--fold-color-warning)}.foldcal-chip[data-tone=alert] .foldcal-chip-bar{background:var(--fold-color-alert)}.foldcal-chip[data-tone=muted] .foldcal-chip-bar{background:var(--fold-color-text-faded)}.foldcal-chip[data-tone=muted] .foldcal-chip-label{color:var(--fold-color-text-secondary);text-decoration:line-through;text-decoration-thickness:1px}@media(forced-colors:active){.foldcal-chip{border:1px solid CanvasText}.foldcal-chip-bar{forced-color-adjust:none}}@media(prefers-reduced-motion:reduce){.foldcal-chip{transition:none}}.foldcala{display:flex;flex-direction:column;min-height:0;width:var(--fold-calendar-agenda-width, 320px);height:var(--fold-calendar-agenda-height, 100%);border:1px solid var(--fold-color-border);border-radius:var(--fold-radius-lg);background:var(--fold-color-surface-card);overflow:clip}.foldcala.is-collapsed{width:var(--fold-calendar-agenda-spine-width, 44px)}.foldcala-spine{flex:1;display:flex;flex-direction:column;align-items:center;gap:var(--fold-space-md);padding:var(--fold-space-md) 0;border:0;background:transparent;color:var(--fold-color-text-secondary);font:inherit;cursor:pointer}.foldcala-spine:hover{background:var(--fold-color-surface-hover);color:var(--fold-color-text)}.foldcala-spine:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px}.foldcala-spine-label{writing-mode:vertical-rl;font-size:var(--fold-text-xs);font-weight:600;letter-spacing:.06em;text-transform:uppercase}.foldcala-spine-count{display:inline-flex;align-items:center;justify-content:center;min-width:var(--fold-calendar-agenda-badge-size, 20px);padding:0 var(--fold-space-xs);border-radius:var(--fold-radius-pill);background:var(--fold-color-warning-surface);color:var(--fold-color-warning-text);font-size:var(--fold-text-xs);font-weight:700;font-variant-numeric:tabular-nums}.foldcala-head{display:flex;align-items:center;justify-content:space-between;gap:var(--fold-space-sm);padding:var(--fold-space-md);border-bottom:1px solid var(--fold-color-border-subtle)}.foldcala-title{display:inline-flex;align-items:center;gap:var(--fold-space-sm);color:var(--fold-color-text);font-size:var(--fold-text-sm);font-weight:700}.foldcala-count{display:inline-flex;align-items:center;justify-content:center;min-width:var(--fold-calendar-agenda-badge-size, 20px);padding:0 var(--fold-space-xs);border-radius:var(--fold-radius-pill);background:var(--fold-color-warning-surface);color:var(--fold-color-warning-text);font-size:var(--fold-text-xs);font-weight:700;font-variant-numeric:tabular-nums}.foldcala-head-right{display:inline-flex;align-items:center;gap:var(--fold-space-xs)}.foldcala-modes{display:inline-flex;padding:2px;border-radius:var(--fold-radius-sm);background:var(--fold-color-surface-sunken)}.foldcala-modes button{padding:2px var(--fold-space-sm);border:0;border-radius:var(--fold-radius-xs);background:transparent;color:var(--fold-color-text-secondary);font:inherit;font-size:var(--fold-text-xs);font-weight:500;cursor:pointer}.foldcala-modes button.is-on{background:var(--fold-color-surface-raised);color:var(--fold-color-text)}.foldcala-modes button:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:1px}.foldcala-collapse{display:inline-flex;align-items:center;justify-content:center;width:var(--fold-calendar-agenda-control-size, 26px);height:var(--fold-calendar-agenda-control-size, 26px);padding:0;border:1px solid var(--fold-color-border-subtle);border-radius:var(--fold-radius-sm);background:var(--fold-color-surface-subtle);color:var(--fold-color-text-secondary);cursor:pointer}.foldcala-collapse:hover{background:var(--fold-color-surface-hover);color:var(--fold-color-text)}.foldcala-collapse:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:1px}.foldcala-body{flex:1;min-height:0;overflow-y:auto;padding:var(--fold-space-sm)}.foldcala-group{display:grid;grid-template-columns:auto minmax(0,1fr);gap:var(--fold-space-sm);padding:var(--fold-space-sm) 0}.foldcala-group+.foldcala-group{border-top:1px solid var(--fold-color-border-subtle)}.foldcala-date{display:flex;align-items:baseline;gap:var(--fold-space-xs);padding:0;border:0;background:transparent;color:inherit;font:inherit;text-align:start;cursor:pointer}.foldcala-date:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:2px}.foldcala-daynum{color:var(--fold-color-text);font-size:var(--fold-text-lg);font-weight:700;font-variant-numeric:tabular-nums;line-height:1}.foldcala-dmeta{display:flex;flex-direction:column}.foldcala-dname{color:var(--fold-color-text-secondary);font-size:var(--fold-text-xs);font-weight:600;letter-spacing:.04em;text-transform:uppercase}.foldcala-dmonth{color:var(--fold-color-text-secondary);font-size:var(--fold-text-xs);text-transform:uppercase}.foldcala-list{display:flex;flex-direction:column;gap:var(--fold-space-xs);margin:0;padding:0;list-style:none}.foldcala-empty{margin:0;padding:var(--fold-space-xl) var(--fold-space-md);color:var(--fold-color-text-secondary);font-size:var(--fold-text-sm);text-align:center}@media(forced-colors:active){.foldcala-modes button.is-on{outline:1px solid Highlight}}\n"] }]
11736
+ }], propDecorators: { from: [{ type: i0.Input, args: [{ isSignal: true, alias: "from", required: true }] }], events: [{ type: i0.Input, args: [{ isSignal: true, alias: "events", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }, { type: i0.Output, args: ["modeChange"] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }, { type: i0.Output, args: ["collapsedChange"] }], limit: [{ type: i0.Input, args: [{ isSignal: true, alias: "limit", required: false }] }], isActionable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isActionable", required: false }] }], dayClick: [{ type: i0.Output, args: ["dayClick"] }], eventClick: [{ type: i0.Output, args: ["eventClick"] }], projectedHeading: [{ type: i0.ContentChild, args: [i0.forwardRef(() => FoldCalendarHeadingDirective), { isSignal: true }] }] } });
11737
+
11738
+ /**
11739
+ * `<fold-calendar-source-filter>` — the chips that switch each feed of a
11740
+ * merged calendar on and off.
11741
+ *
11742
+ * A calendar usually shows several feeds at once (a programme, staff leave,
11743
+ * contracts). This owns the **selection** only: it counts what each feed
11744
+ * contributes and toggles it, and the caller runs `foldFilterBySource()` over
11745
+ * its own events. Keeping the two apart means the chips never need to know how
11746
+ * the events are fetched, and the filtering stays a pure function.
11747
+ *
11748
+ * ## Inputs
11749
+ * | Input | Type | Default | Meaning |
11750
+ * |-----------|--------------------------|---------|---------|
11751
+ * | `sources` | `FoldCalendarSource[]` | `[]` | The feeds to offer. |
11752
+ * | `events` | `FoldCalendarEvent[]` | `[]` | Counted per feed — pass the window on screen for live counts. |
11753
+ * | `active` | `ReadonlySet<string> \| null` | `null` | **Two-way.** The keys currently shown; `null` means "all". |
11754
+ * | `labels` | `Partial<FoldCalendarLabels>` | — | Per-instance label overrides. |
11755
+ *
11756
+ * ## Outputs
11757
+ * | Output | Payload | Fires on |
11758
+ * |-------------------|-----------------------|----------|
11759
+ * | `selectionChange` | `ReadonlySet<string>` | A chip toggled — never `null`. |
11760
+ *
11761
+ * `null` and an empty `Set` mean opposite things — "nothing switched off yet"
11762
+ * versus "every feed switched off" — which is why they are not collapsed.
11763
+ * `foldFilterBySource()` takes either.
11764
+ *
11765
+ * ## Accessibility
11766
+ * Each chip is a **toggle**, so it carries `aria-pressed` in both states, and
11767
+ * its accessible name says which feed and whether it is showing — the tick and
11768
+ * the dot are colour, which cannot be the only carrier.
11769
+ *
11770
+ * @selector `fold-calendar-source-filter`
11771
+ *
11772
+ * @example
11773
+ * ```html
11774
+ * <fold-calendar-source-filter
11775
+ * [sources]="sources"
11776
+ * [events]="events()"
11777
+ * [(active)]="activeSources"
11778
+ * />
11779
+ * <fold-calendar-month [events]="visibleEvents()" [(month)]="month" />
11780
+ * ```
11781
+ */
11782
+ class FoldCalendarSourceFilterComponent {
11783
+ /** The feeds to offer. */
11784
+ sources = input([], /* @ts-ignore */
11785
+ ...(ngDevMode ? [{ debugName: "sources" }] : /* istanbul ignore next */ []));
11786
+ /** Events to count per feed; pass the window on screen for live counts. */
11787
+ events = input([], /* @ts-ignore */
11788
+ ...(ngDevMode ? [{ debugName: "events" }] : /* istanbul ignore next */ []));
11789
+ /**
11790
+ * The keys currently shown. Left unset every feed shows, so the filter is
11791
+ * useful before the caller has any selection state of its own.
11792
+ */
11793
+ active = model(null, /* @ts-ignore */
11794
+ ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
11795
+ /**
11796
+ * The selection after a chip was toggled — **never `null`**, unlike `active`,
11797
+ * whose unset state exists only as a starting value. Handle this one and you
11798
+ * never have to eliminate a case that cannot happen.
11799
+ */
11800
+ selectionChange = output();
11801
+ /** Labels and locale — see the directive. */
11802
+ chrome = inject(FoldCalendarChromeDirective);
11803
+ /** The effective selection — every key while `active` is unset. */
11804
+ shownKeys = computed(() => {
11805
+ const selected = this.active();
11806
+ if (selected !== null) {
11807
+ return selected;
11808
+ }
11809
+ return new Set(this.sources().map((source) => source.key));
11810
+ }, /* @ts-ignore */
11811
+ ...(ngDevMode ? [{ debugName: "shownKeys" }] : /* istanbul ignore next */ []));
11812
+ chips = computed(() => {
11813
+ const shown = this.shownKeys();
11814
+ const events = this.events();
11815
+ return this.sources().map((source) => ({
11816
+ source,
11817
+ count: events.filter((event) => event.sourceKey === source.key).length,
11818
+ shown: shown.has(source.key),
11819
+ }));
11820
+ }, /* @ts-ignore */
11821
+ ...(ngDevMode ? [{ debugName: "chips" }] : /* istanbul ignore next */ []));
11822
+ chipLabel(chip) {
11823
+ return this.chrome.l().sourceState(chip.source.label, chip.shown);
11824
+ }
11825
+ /** Writes the next selection rather than mutating the set in place. */
11826
+ toggle(chip) {
11827
+ const next = new Set(this.shownKeys());
11828
+ if (next.has(chip.source.key)) {
11829
+ next.delete(chip.source.key);
11830
+ }
11831
+ else {
11832
+ next.add(chip.source.key);
11833
+ }
11834
+ this.active.set(next);
11835
+ this.selectionChange.emit(next);
11836
+ }
11837
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCalendarSourceFilterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11838
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldCalendarSourceFilterComponent, isStandalone: true, selector: "fold-calendar-source-filter", inputs: { sources: { classPropertyName: "sources", publicName: "sources", isSignal: true, isRequired: false, transformFunction: null }, events: { classPropertyName: "events", publicName: "events", isSignal: true, isRequired: false, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { active: "activeChange", selectionChange: "selectionChange" }, hostDirectives: [{ directive: FoldCalendarChromeDirective, inputs: ["locale", "locale", "labels", "labels", "formats", "formats"] }], ngImport: i0, template: "<nav class=\"foldcalsf\" [attr.aria-label]=\"chrome.l().sourceFilter\">\n @for (chip of chips(); track chip.source.key) {\n <button\n type=\"button\"\n class=\"foldcalsf-chip\"\n [class.is-on]=\"chip.shown\"\n [attr.data-tone]=\"chip.source.tone ?? 'neutral'\"\n [attr.aria-pressed]=\"chip.shown\"\n [attr.aria-label]=\"chipLabel(chip)\"\n (click)=\"toggle(chip)\"\n >\n @if (chip.shown) {\n <fold-icon class=\"foldcalsf-check\" name=\"check\" size=\"sm\" />\n }\n <span class=\"foldcalsf-dot\" aria-hidden=\"true\"></span>\n <span class=\"foldcalsf-label\">{{ chip.source.label }}</span>\n <span class=\"foldcalsf-count\">{{ chip.count }}</span>\n </button>\n }\n</nav>\n", styles: [":host{display:block}.foldcalsf{display:flex;flex-wrap:wrap;gap:var(--fold-space-sm)}.foldcalsf-chip{display:inline-flex;align-items:center;gap:var(--fold-space-xs);padding:var(--fold-space-xs) var(--fold-space-md);border:1px solid var(--fold-color-border);border-radius:var(--fold-radius-pill);background:var(--fold-color-surface-subtle);color:var(--fold-color-text-secondary);font:inherit;font-size:var(--fold-text-sm);font-weight:500;cursor:pointer;transition:background-color var(--fold-motion-fast, .12s) ease,color var(--fold-motion-fast, .12s) ease,border-color var(--fold-motion-fast, .12s) ease}.foldcalsf-chip:hover{background:var(--fold-color-surface-hover)}.foldcalsf-chip:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:2px}.foldcalsf-chip:not(.is-on) .foldcalsf-dot{background:var(--fold-color-text-faded)}.foldcalsf-chip:not(.is-on) .foldcalsf-label{text-decoration:line-through;text-decoration-color:var(--fold-color-text-faded)}.foldcalsf-chip.is-on{background:var(--fold-color-surface-raised);border-color:var(--fold-color-border);color:var(--fold-color-text)}.foldcalsf-check{flex:none;color:var(--fold-color-primary-text)}.foldcalsf-dot{flex:none;width:8px;height:8px;border-radius:var(--fold-radius-round);background:var(--fold-color-text-muted)}.foldcalsf-chip[data-tone=success].is-on .foldcalsf-dot{background:var(--fold-color-success)}.foldcalsf-chip[data-tone=warning].is-on .foldcalsf-dot{background:var(--fold-color-warning)}.foldcalsf-chip[data-tone=alert].is-on .foldcalsf-dot{background:var(--fold-color-alert)}.foldcalsf-chip[data-tone=muted].is-on .foldcalsf-dot{background:var(--fold-color-text-faded)}.foldcalsf-count{color:var(--fold-color-text-secondary);font-size:var(--fold-text-xs);font-variant-numeric:tabular-nums}@media(forced-colors:active){.foldcalsf-chip.is-on{border:2px solid Highlight}}@media(prefers-reduced-motion:reduce){.foldcalsf-chip{transition:none}}\n"], dependencies: [{ kind: "component", type: FoldIconComponent, selector: "fold-icon", inputs: ["name", "size", "tone", "title"] }] });
11839
+ }
11840
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCalendarSourceFilterComponent, decorators: [{
11841
+ type: Component,
11842
+ args: [{ selector: "fold-calendar-source-filter", standalone: true, imports: [FoldIconComponent], hostDirectives: [
11843
+ {
11844
+ directive: FoldCalendarChromeDirective,
11845
+ inputs: ["locale", "labels", "formats"],
11846
+ },
11847
+ ], template: "<nav class=\"foldcalsf\" [attr.aria-label]=\"chrome.l().sourceFilter\">\n @for (chip of chips(); track chip.source.key) {\n <button\n type=\"button\"\n class=\"foldcalsf-chip\"\n [class.is-on]=\"chip.shown\"\n [attr.data-tone]=\"chip.source.tone ?? 'neutral'\"\n [attr.aria-pressed]=\"chip.shown\"\n [attr.aria-label]=\"chipLabel(chip)\"\n (click)=\"toggle(chip)\"\n >\n @if (chip.shown) {\n <fold-icon class=\"foldcalsf-check\" name=\"check\" size=\"sm\" />\n }\n <span class=\"foldcalsf-dot\" aria-hidden=\"true\"></span>\n <span class=\"foldcalsf-label\">{{ chip.source.label }}</span>\n <span class=\"foldcalsf-count\">{{ chip.count }}</span>\n </button>\n }\n</nav>\n", styles: [":host{display:block}.foldcalsf{display:flex;flex-wrap:wrap;gap:var(--fold-space-sm)}.foldcalsf-chip{display:inline-flex;align-items:center;gap:var(--fold-space-xs);padding:var(--fold-space-xs) var(--fold-space-md);border:1px solid var(--fold-color-border);border-radius:var(--fold-radius-pill);background:var(--fold-color-surface-subtle);color:var(--fold-color-text-secondary);font:inherit;font-size:var(--fold-text-sm);font-weight:500;cursor:pointer;transition:background-color var(--fold-motion-fast, .12s) ease,color var(--fold-motion-fast, .12s) ease,border-color var(--fold-motion-fast, .12s) ease}.foldcalsf-chip:hover{background:var(--fold-color-surface-hover)}.foldcalsf-chip:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:2px}.foldcalsf-chip:not(.is-on) .foldcalsf-dot{background:var(--fold-color-text-faded)}.foldcalsf-chip:not(.is-on) .foldcalsf-label{text-decoration:line-through;text-decoration-color:var(--fold-color-text-faded)}.foldcalsf-chip.is-on{background:var(--fold-color-surface-raised);border-color:var(--fold-color-border);color:var(--fold-color-text)}.foldcalsf-check{flex:none;color:var(--fold-color-primary-text)}.foldcalsf-dot{flex:none;width:8px;height:8px;border-radius:var(--fold-radius-round);background:var(--fold-color-text-muted)}.foldcalsf-chip[data-tone=success].is-on .foldcalsf-dot{background:var(--fold-color-success)}.foldcalsf-chip[data-tone=warning].is-on .foldcalsf-dot{background:var(--fold-color-warning)}.foldcalsf-chip[data-tone=alert].is-on .foldcalsf-dot{background:var(--fold-color-alert)}.foldcalsf-chip[data-tone=muted].is-on .foldcalsf-dot{background:var(--fold-color-text-faded)}.foldcalsf-count{color:var(--fold-color-text-secondary);font-size:var(--fold-text-xs);font-variant-numeric:tabular-nums}@media(forced-colors:active){.foldcalsf-chip.is-on{border:2px solid Highlight}}@media(prefers-reduced-motion:reduce){.foldcalsf-chip{transition:none}}\n"] }]
11848
+ }], propDecorators: { sources: [{ type: i0.Input, args: [{ isSignal: true, alias: "sources", required: false }] }], events: [{ type: i0.Input, args: [{ isSignal: true, alias: "events", required: false }] }], active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }, { type: i0.Output, args: ["activeChange"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }] } });
11849
+
9028
11850
  // Public TypeScript API for fold-ng.
9029
11851
  //
9030
11852
  // The tokens themselves ship as CSS (import 'fold-ng/tokens.css').
@@ -9035,5 +11857,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
9035
11857
  * Generated bundle index. Do not edit.
9036
11858
  */
9037
11859
 
9038
- export { FOLD_AUTO_PALETTES, FOLD_BLUR_TOKENS, FOLD_BUILTIN_ICONS, FOLD_DATA_TABLE_DEFAULT_LABELS, FOLD_DATA_TABLE_LABELS, FOLD_INLINE_CONFIRM_DEFAULT_LABELS, FOLD_INLINE_CONFIRM_LABELS, FOLD_MOTION_TOKENS, FOLD_PAGINATOR_DEFAULT_LABELS, FOLD_PAGINATOR_LABELS, FOLD_PALETTE_DEFAULT, FOLD_PANEL_CLOSE_LABEL, FOLD_PANEL_DEFAULTS, FOLD_RADIUS_TOKENS, FOLD_SEMANTIC_COLOR_TOKENS, FOLD_SHADOW_TOKENS, FOLD_SPACE_TOKENS, FOLD_TEXT_TOKENS, FOLD_TOAST_CONFIG, FoldAppShellComponent, FoldAsideLayoutComponent, FoldAvatarComponent, FoldAvatarDetailComponent, FoldAvatarListComponent, FoldBadgeComponent, FoldButtonComponent, FoldButtonIconComponent, FoldCalloutComponent, FoldCardComponent, FoldCheckboxComponent, FoldChoiceRowComponent, FoldContextCardComponent, FoldDataTableCellDirective, FoldDataTableComponent, FoldDataTableRowCardDirective, FoldDisclosureComponent, FoldDropdownComponent, FoldDropdownItemComponent, FoldElementTitleComponent, FoldElevatedDirective, FoldEmptyStateComponent, FoldFieldComponent, FoldFieldIdDirective, FoldFieldListComponent, FoldFileDropzoneComponent, FoldHeroCardComponent, FoldHeroSectionComponent, FoldIconComponent, FoldIconRegistry, FoldIdService, FoldInlineConfirmComponent, FoldInputComponent, FoldLinkComponent, FoldListboxComponent, FoldLoadingStateComponent, FoldMenuComponent, FoldMenuItemComponent, FoldMenuSectionComponent, FoldMenuSeparatorComponent, FoldMultiselectComponent, FoldNavLauncherComponent, FoldNavLayoutComponent, FoldNavTileComponent, FoldNumberInputComponent, FoldOptionComponent, FoldPageLayoutComponent, FoldPageSectionComponent, FoldPageTitleDirective, FoldPaginatorComponent, FoldPaletteRegistry, FoldPanelComponentOutletDirective, FoldPanelHeaderComponent, FoldPanelHostComponent, FoldPanelHostService, FoldPanelRef, FoldPanelToggle, FoldPasswordFieldComponent, FoldPopoverComponent, FoldPopoverTriggerDirective, FoldRangeSliderComponent, FoldRepeatPressDirective, FoldSearchComponent, FoldSelectComponent, FoldSliderComponent, FoldSpinnerComponent, FoldStatusBadgeComponent, FoldStickyColumnDirective, FoldSurfaceDirective, FoldTabPanelComponent, FoldTabsComponent, FoldTimelineComponent, FoldToastComponent, FoldToastContainerComponent, FoldToastService, FoldToggleIconComponent, FoldViewNavComponent, FoldViewToggleComponent, autoUpdate, computePlacement, foldBlurVar, foldCardBandChrome, foldColorProperty, foldColorVar, foldDefaultPasswordRules, foldHashSeed, foldMotionVar, foldRadiusVar, foldRegexRule, foldResolvePalette, foldShadowVar, foldSpaceVar, foldTextVar, observeElementWidth, provideFoldDataTableLabels, provideFoldIcons, provideFoldInlineConfirmLabels, provideFoldPaginatorLabels, provideFoldPalette, provideFoldPanelDefaults, provideFoldPanelLabels, provideFoldToasts };
11860
+ export { FOLD_AUTO_PALETTES, FOLD_BLUR_TOKENS, FOLD_BUILTIN_ICONS, FOLD_CALENDAR_DEFAULT_LABELS, FOLD_CALENDAR_FORMATS, FOLD_CALENDAR_LABELS, FOLD_DATA_TABLE_DEFAULT_LABELS, FOLD_DATA_TABLE_LABELS, FOLD_DEFAULT_WEEKEND_DAYS, FOLD_FALLBACK_WEEK_INFO, FOLD_INLINE_CONFIRM_DEFAULT_LABELS, FOLD_INLINE_CONFIRM_LABELS, FOLD_MINUTES_PER_DAY, FOLD_MOTION_TOKENS, FOLD_PAGINATOR_DEFAULT_LABELS, FOLD_PAGINATOR_LABELS, FOLD_PALETTE_DEFAULT, FOLD_PANEL_CLOSE_LABEL, FOLD_PANEL_DEFAULTS, FOLD_RADIUS_TOKENS, FOLD_SEMANTIC_COLOR_TOKENS, FOLD_SHADOW_TOKENS, FOLD_SPACE_TOKENS, FOLD_TEXT_TOKENS, FOLD_TOAST_CONFIG, FoldAppShellComponent, FoldAsideLayoutComponent, FoldAvatarComponent, FoldAvatarDetailComponent, FoldAvatarListComponent, FoldBadgeComponent, FoldButtonComponent, FoldButtonIconComponent, FoldCalendarAgendaComponent, FoldCalendarChromeDirective, FoldCalendarDayComponent, FoldCalendarDayDirective, FoldCalendarEventDirective, FoldCalendarHeadingDirective, FoldCalendarListComponent, FoldCalendarMonthComponent, FoldCalendarOverflowDirective, FoldCalendarSourceFilterComponent, FoldCalendarTimegridComponent, FoldCalendarTitleDirective, FoldCalendarToolbarComponent, FoldCalendarWeekComponent, FoldCalloutComponent, FoldCardComponent, FoldCheckboxComponent, FoldChoiceRowComponent, FoldContextCardComponent, FoldDataTableCellDirective, FoldDataTableComponent, FoldDataTableRowCardDirective, FoldDisclosureComponent, FoldDropdownComponent, FoldDropdownItemComponent, FoldElementTitleComponent, FoldElevatedDirective, FoldEmptyStateComponent, FoldFieldComponent, FoldFieldIdDirective, FoldFieldListComponent, FoldFileDropzoneComponent, FoldHeroCardComponent, FoldHeroSectionComponent, FoldIconComponent, FoldIconRegistry, FoldIdService, FoldInlineConfirmComponent, FoldInputComponent, FoldLinkComponent, FoldListboxComponent, FoldLoadingStateComponent, FoldMenuComponent, FoldMenuItemComponent, FoldMenuSectionComponent, FoldMenuSeparatorComponent, FoldMultiselectComponent, FoldNavLauncherComponent, FoldNavLayoutComponent, FoldNavTileComponent, FoldNumberInputComponent, FoldOptionComponent, FoldPageLayoutComponent, FoldPageSectionComponent, FoldPageTitleDirective, FoldPaginatorComponent, FoldPaletteRegistry, FoldPanelComponentOutletDirective, FoldPanelHeaderComponent, FoldPanelHostComponent, FoldPanelHostService, FoldPanelRef, FoldPanelToggle, FoldPasswordFieldComponent, FoldPopoverComponent, FoldPopoverTriggerDirective, FoldRangeSliderComponent, FoldRepeatPressDirective, FoldSearchComponent, FoldSelectComponent, FoldSliderComponent, FoldSpinnerComponent, FoldStatusBadgeComponent, FoldStickyColumnDirective, FoldSurfaceDirective, FoldTabPanelComponent, FoldTabsComponent, FoldTimelineComponent, FoldToastComponent, FoldToastContainerComponent, FoldToastService, FoldToggleIconComponent, FoldViewNavComponent, FoldViewToggleComponent, autoUpdate, computePlacement, foldAddDays, foldAddMinutes, foldAddMonths, foldBlurVar, foldBuildAgenda, foldBuildDay, foldBuildMonthGrid, foldBuildTimeGrid, foldBuildWeek, foldCalendarNextFocus, foldCardBandChrome, foldColorProperty, foldColorVar, foldCountActionable, foldDaysBetween, foldDefaultPasswordRules, foldEndOfMonth, foldEventsInRange, foldEventsOnDay, foldFilterBySource, foldFromMinutes, foldFromNativeDate, foldFromTemporal, foldHashSeed, foldIsActionable, foldIsCalendarDate, foldIsCalendarTime, foldIsTimed, foldIsWeekend, foldIsoWeek, foldIsoWeekYear, foldLayOutOverlaps, foldLocaleWeekInfo, foldMotionVar, foldRadiusVar, foldRangeForView, foldRegexRule, foldResolvePalette, foldShadowVar, foldShiftDate, foldSpaceVar, foldStartOfMonth, foldStartOfWeek, foldTextVar, foldToMinutes, foldToNativeDate, foldToday, foldViewTitle, foldWeekdayIndex, foldWeekdayOf, observeElementWidth, provideFoldCalendarLabels, provideFoldDataTableLabels, provideFoldIcons, provideFoldInlineConfirmLabels, provideFoldPaginatorLabels, provideFoldPalette, provideFoldPanelDefaults, provideFoldPanelLabels, provideFoldToasts };
9039
11861
  //# sourceMappingURL=fold-ng.mjs.map