@nomideusz/svelte-calendar 1.4.1 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -54
- package/dist/views/agenda/AgendaWeek.svelte +4 -1
- package/dist/views/mobile/MobileDay.svelte +2 -0
- package/dist/views/month/MonthGrid.svelte +5 -0
- package/dist/views/planner/PlannerScroll.svelte +5 -0
- package/dist/views/planner/PlannerWeek.svelte +73 -9
- package/package.json +1 -1
- package/widget/widget.js +147 -130
package/README.md
CHANGED
|
@@ -580,55 +580,7 @@ The calendar reads its events through an adapter. Which one:
|
|
|
580
580
|
| [`createCompositeAdapter(adapters)`](#composite-adapter) | One calendar shows several sources, e.g. a recurring timetable plus one-off events. |
|
|
581
581
|
| [Custom `CalendarAdapter`](#custom-adapter) | None of the above fits: implement `fetchEvents`, plus the writes you need. |
|
|
582
582
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
For fixed repeating events (class timetables, office hours):
|
|
586
|
-
|
|
587
|
-
```svelte
|
|
588
|
-
<script>
|
|
589
|
-
import { Calendar, createRecurringAdapter } from '@nomideusz/svelte-calendar';
|
|
590
|
-
|
|
591
|
-
const adapter = createRecurringAdapter([
|
|
592
|
-
{ id: '1', title: 'Yoga', dayOfWeek: 1, startTime: '07:00', endTime: '08:30' },
|
|
593
|
-
{ id: '2', title: 'Standup', frequency: 'daily', startTime: '09:00', endTime: '09:15',
|
|
594
|
-
startDate: '2026-03-01', until: '2026-03-31' },
|
|
595
|
-
{ id: '3', title: 'Review', frequency: 'monthly', dayOfMonth: 15,
|
|
596
|
-
startTime: '10:00', endTime: '11:00' },
|
|
597
|
-
{ id: '4', title: 'Night shift', dayOfWeek: [5, 6], startTime: '22:00', endTime: '02:00' },
|
|
598
|
-
]);
|
|
599
|
-
</script>
|
|
600
|
-
|
|
601
|
-
<Calendar {adapter} view="week-planner" />
|
|
602
|
-
```
|
|
603
|
-
|
|
604
|
-
<details>
|
|
605
|
-
<summary>RecurringEvent fields</summary>
|
|
606
|
-
|
|
607
|
-
| Field | Type | Default | Description |
|
|
608
|
-
|-------|------|---------|-------------|
|
|
609
|
-
| `id` | `string` | *required* | Rule identifier |
|
|
610
|
-
| `title` | `string` | *required* | Event title |
|
|
611
|
-
| `startTime` | `string` | *required* | Start time `"HH:MM"`, 24-hour |
|
|
612
|
-
| `endTime` | `string` | *required* | End time `"HH:MM"`. Earlier than `startTime` → ends the next day (overnight). |
|
|
613
|
-
| `frequency` | `'daily' \| 'weekly' \| 'monthly'` | `'weekly'` | Recurrence frequency |
|
|
614
|
-
| `interval` | `number` | `1` | Every N periods (`2` + weekly = biweekly). Needs `startDate` when > 1. |
|
|
615
|
-
| `dayOfWeek` | `number \| number[]` | — | ISO weekday 1=Mon…7=Sun. Required for weekly. |
|
|
616
|
-
| `dayOfMonth` | `number` | `1` | Day of month (1–31) for monthly; clamped to shorter months. |
|
|
617
|
-
| `startDate` | `string` | — | First possible occurrence `"YYYY-MM-DD"` |
|
|
618
|
-
| `until` | `string` | — | Last possible occurrence `"YYYY-MM-DD"` |
|
|
619
|
-
| `count` | `number` | — | Max occurrences from `startDate` (requires it). With `until`, the stricter wins. |
|
|
620
|
-
| `excludeDates` | `string[]` | — | `"YYYY-MM-DD"` dates this rule skips |
|
|
621
|
-
| `color` | `string` | — | Bar color; else from `palette`, else ink (`--dt-event`) |
|
|
622
|
-
| `subtitle` / `tags` / `category` / `location` / `resourceId` | | — | Copied to every occurrence |
|
|
623
|
-
| `data` | `Record<string, unknown>` | — | Copied to every occurrence, plus `recurringId` |
|
|
624
|
-
|
|
625
|
-
</details>
|
|
626
|
-
|
|
627
|
-
Options (`RecurringAdapterOptions`): `mondayStart` (default `true`), `palette` (colors rules without a `color`; off by default), `movable` (default `false`).
|
|
628
|
-
|
|
629
|
-
Occurrences get the id `` `${rule.id}--YYYYMMDD` `` and `data.recurringId`. The adapter projects and stores nothing, so by default every occurrence carries `data.readOnly` and can't be dragged (it can still be clicked). With `movable: true` the views allow the drag and the adapter refuses the write with a read-only error, so `oneventmove` fires and the host stores the move: add the date to the rule's `excludeDates` and keep the moved occurrence with your one-off events (for example the memory side of a [composite adapter](#composite-adapter)).
|
|
630
|
-
|
|
631
|
-
## REST Adapter
|
|
583
|
+
### REST Adapter
|
|
632
584
|
|
|
633
585
|
Connect to any REST API:
|
|
634
586
|
|
|
@@ -653,7 +605,7 @@ const adapter = createRestAdapter({
|
|
|
653
605
|
|
|
654
606
|
The adapter calls `GET {baseUrl}/events?start=…&end=…` (ISO strings), `POST /events`, `PATCH /events/:id` and `DELETE /events/:id`, sending JSON with your `headers`. By default a list response must be an array of events and a single response one event; `start`/`end` are parsed into `Date`s. Override with `mapEvents(data)` for lists and `mapEvent(data)` for single events. A non-2xx response or invalid JSON throws.
|
|
655
607
|
|
|
656
|
-
|
|
608
|
+
### JMAP Adapter
|
|
657
609
|
|
|
658
610
|
Read events from a [JMAP Calendars](https://jmap.io/) server (Fastmail, Stalwart, Cyrus):
|
|
659
611
|
|
|
@@ -678,7 +630,7 @@ const adapter = createJmapAdapter(client, {
|
|
|
678
630
|
|
|
679
631
|
Read-only. Expands recurrences server-side and handles all-day events, ISO-duration ends and per-calendar colors (pass `calendars: [{ id, name, color }]`, or a function returning them).
|
|
680
632
|
|
|
681
|
-
|
|
633
|
+
### Mapped Adapter
|
|
682
634
|
|
|
683
635
|
Wrap any static array of external records (class schedules, appointments, timetables) without writing a custom adapter. Supply a declarative field mapping (or a `mapEvent` function for full control) and the adapter handles parsing, colors, tags and status:
|
|
684
636
|
|
|
@@ -721,7 +673,7 @@ const adapter = createMappedAdapter(rawData, {
|
|
|
721
673
|
|
|
722
674
|
Mapped adapters are read-only by default (writes throw a read-only error, so drags reach `oneventmove`). Set `readOnly: false` to allow writes; supply `onMutate: { onCreate, onUpdate, onDelete }` to persist them (each returns the stored source record, which is mapped again), or omit it to keep changes in memory.
|
|
723
675
|
|
|
724
|
-
|
|
676
|
+
### Composite Adapter
|
|
725
677
|
|
|
726
678
|
Merge several adapters into one, e.g. a recurring weekly schedule plus one-off events:
|
|
727
679
|
|
|
@@ -746,7 +698,7 @@ const adapter = createCompositeAdapter([recurring, memory], {
|
|
|
746
698
|
});
|
|
747
699
|
```
|
|
748
700
|
|
|
749
|
-
|
|
701
|
+
### Custom Adapter
|
|
750
702
|
|
|
751
703
|
Implement the `CalendarAdapter` interface to connect any data source. Only `fetchEvents` is required:
|
|
752
704
|
|
|
@@ -778,7 +730,7 @@ const adapter = withInitialEvents(createRestAdapter({ baseUrl: '/api' }), data.e
|
|
|
778
730
|
|
|
779
731
|
The first load is answered synchronously from the seed; every later load (navigation, refresh) goes to the wrapped adapter.
|
|
780
732
|
|
|
781
|
-
|
|
733
|
+
#### Refusing a write
|
|
782
734
|
|
|
783
735
|
The Calendar tells two refusals apart when `updateEvent` throws:
|
|
784
736
|
|
|
@@ -790,6 +742,54 @@ The Calendar tells two refusals apart when `updateEvent` throws:
|
|
|
790
742
|
|
|
791
743
|
An adapter without `updateEvent` counts as read-only. Plain `Error`s whose message contains `read-only` (lower case) or `not found` (any case) are read the same way, so older adapters keep working. `isReadOnlyError(e)` and `isNotFoundError(e)` test both forms.
|
|
792
744
|
|
|
745
|
+
## Recurring Schedules
|
|
746
|
+
|
|
747
|
+
For fixed repeating events (class timetables, office hours):
|
|
748
|
+
|
|
749
|
+
```svelte
|
|
750
|
+
<script>
|
|
751
|
+
import { Calendar, createRecurringAdapter } from '@nomideusz/svelte-calendar';
|
|
752
|
+
|
|
753
|
+
const adapter = createRecurringAdapter([
|
|
754
|
+
{ id: '1', title: 'Yoga', dayOfWeek: 1, startTime: '07:00', endTime: '08:30' },
|
|
755
|
+
{ id: '2', title: 'Standup', frequency: 'daily', startTime: '09:00', endTime: '09:15',
|
|
756
|
+
startDate: '2026-03-01', until: '2026-03-31' },
|
|
757
|
+
{ id: '3', title: 'Review', frequency: 'monthly', dayOfMonth: 15,
|
|
758
|
+
startTime: '10:00', endTime: '11:00' },
|
|
759
|
+
{ id: '4', title: 'Night shift', dayOfWeek: [5, 6], startTime: '22:00', endTime: '02:00' },
|
|
760
|
+
]);
|
|
761
|
+
</script>
|
|
762
|
+
|
|
763
|
+
<Calendar {adapter} view="week-planner" />
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
<details>
|
|
767
|
+
<summary>RecurringEvent fields</summary>
|
|
768
|
+
|
|
769
|
+
| Field | Type | Default | Description |
|
|
770
|
+
|-------|------|---------|-------------|
|
|
771
|
+
| `id` | `string` | *required* | Rule identifier |
|
|
772
|
+
| `title` | `string` | *required* | Event title |
|
|
773
|
+
| `startTime` | `string` | *required* | Start time `"HH:MM"`, 24-hour |
|
|
774
|
+
| `endTime` | `string` | *required* | End time `"HH:MM"`. Earlier than `startTime` → ends the next day (overnight). |
|
|
775
|
+
| `frequency` | `'daily' \| 'weekly' \| 'monthly'` | `'weekly'` | Recurrence frequency |
|
|
776
|
+
| `interval` | `number` | `1` | Every N periods (`2` + weekly = biweekly). Needs `startDate` when > 1. |
|
|
777
|
+
| `dayOfWeek` | `number \| number[]` | — | ISO weekday 1=Mon…7=Sun. Required for weekly. |
|
|
778
|
+
| `dayOfMonth` | `number` | `1` | Day of month (1–31) for monthly; clamped to shorter months. |
|
|
779
|
+
| `startDate` | `string` | — | First possible occurrence `"YYYY-MM-DD"` |
|
|
780
|
+
| `until` | `string` | — | Last possible occurrence `"YYYY-MM-DD"` |
|
|
781
|
+
| `count` | `number` | — | Max occurrences from `startDate` (requires it). With `until`, the stricter wins. |
|
|
782
|
+
| `excludeDates` | `string[]` | — | `"YYYY-MM-DD"` dates this rule skips |
|
|
783
|
+
| `color` | `string` | — | Bar color; else from `palette`, else ink (`--dt-event`) |
|
|
784
|
+
| `subtitle` / `tags` / `category` / `location` / `resourceId` | | — | Copied to every occurrence |
|
|
785
|
+
| `data` | `Record<string, unknown>` | — | Copied to every occurrence, plus `recurringId` |
|
|
786
|
+
|
|
787
|
+
</details>
|
|
788
|
+
|
|
789
|
+
Options (`RecurringAdapterOptions`): `mondayStart` (default `true`), `palette` (colors rules without a `color`; off by default), `movable` (default `false`).
|
|
790
|
+
|
|
791
|
+
Occurrences get the id `` `${rule.id}--YYYYMMDD` `` and `data.recurringId`. The adapter projects and stores nothing, so by default every occurrence carries `data.readOnly` and can't be dragged (it can still be clicked). With `movable: true` the views allow the drag and the adapter refuses the write with a read-only error, so `oneventmove` fires and the host stores the move: add the date to the rule's `excludeDates` and keep the moved occurrence with your one-off events (for example the memory side of a [composite adapter](#composite-adapter)).
|
|
792
|
+
|
|
793
793
|
## Headless API
|
|
794
794
|
|
|
795
795
|
For full control over rendering, skip `<Calendar>` and drive everything from reactive state. `createCalendar()` returns computed layouts, navigation actions, drag helpers and the raw engines. Zero DOM: bring your own UI. Call it during component initialisation.
|
|
@@ -844,8 +844,11 @@ const weekDays = $derived.by(() => {
|
|
|
844
844
|
.ag-wday .ag-wday-badge--muted {
|
|
845
845
|
color: var(--dt-text-2, rgba(15, 15, 15, 0.66));
|
|
846
846
|
}
|
|
847
|
+
/* Set like the weekday beside it, so the heading reads in one case */
|
|
847
848
|
.ag-wday-date {
|
|
848
|
-
font: 400
|
|
849
|
+
font: 400 12px/1 var(--dt-sans, system-ui, sans-serif);
|
|
850
|
+
letter-spacing: 0.08em;
|
|
851
|
+
text-transform: uppercase;
|
|
849
852
|
color: var(--dt-text-2, rgba(15, 15, 15, 0.66));
|
|
850
853
|
}
|
|
851
854
|
|
|
@@ -499,7 +499,12 @@ function chipTime(e) {
|
|
|
499
499
|
overflow: hidden;
|
|
500
500
|
text-overflow: ellipsis;
|
|
501
501
|
white-space: nowrap;
|
|
502
|
+
/* A Latin title in RTL keeps its own direction: the ellipsis cuts its end */
|
|
503
|
+
unicode-bidi: plaintext;
|
|
504
|
+
text-align: left;
|
|
502
505
|
}
|
|
506
|
+
/* …but it sits on the calendar's side, by the bar */
|
|
507
|
+
.mg-chip-title:dir(rtl) { text-align: right; }
|
|
503
508
|
.mg-more {
|
|
504
509
|
align-self: flex-start;
|
|
505
510
|
border: none;
|
|
@@ -1446,7 +1446,12 @@ function onCellKeydown(e, ms) {
|
|
|
1446
1446
|
text-overflow: ellipsis;
|
|
1447
1447
|
min-width: 0;
|
|
1448
1448
|
flex: 1 1 auto;
|
|
1449
|
+
/* A Latin title in RTL keeps its own direction: the ellipsis cuts its end */
|
|
1450
|
+
unicode-bidi: plaintext;
|
|
1451
|
+
text-align: left;
|
|
1449
1452
|
}
|
|
1453
|
+
/* …but it sits on the calendar's side, by the bar */
|
|
1454
|
+
.wg-ev-title:dir(rtl) { text-align: right; }
|
|
1450
1455
|
|
|
1451
1456
|
.wg-ev-loc {
|
|
1452
1457
|
font: 400 12.5px / 1 var(--dt-sans, system-ui, sans-serif);
|
|
@@ -305,6 +305,9 @@ const weekIsEmpty = $derived(!events.some((ev) => ev.start.getTime() < weekEndMs
|
|
|
305
305
|
// with room for its label (centred on the line) below the sticky top.
|
|
306
306
|
// An event running across that hour line would be sliced under the sticky
|
|
307
307
|
// top, so the scroll starts at that event's hour instead.
|
|
308
|
+
// A server-rendered grid sits at midnight until this runs: it stays
|
|
309
|
+
// hidden until placed (.tw-body--unplaced), and shows anyway after 1.5s.
|
|
310
|
+
let placed = $state(false);
|
|
308
311
|
$effect(() => {
|
|
309
312
|
void weekStartMs;
|
|
310
313
|
void startHour;
|
|
@@ -339,6 +342,7 @@ $effect(() => {
|
|
|
339
342
|
}
|
|
340
343
|
}
|
|
341
344
|
el.scrollTop = Math.max(0, (hour - startHour) * HOUR_H - LABEL_ROOM);
|
|
345
|
+
placed = true;
|
|
342
346
|
});
|
|
343
347
|
});
|
|
344
348
|
// ── Pointer coalescing ─────────────────────────────
|
|
@@ -442,6 +446,9 @@ const TIME_LINE = 11 * 1.1;
|
|
|
442
446
|
const TITLE_LINE = 12 * 1.2;
|
|
443
447
|
const ROOM_LINE = 10 * 1.2;
|
|
444
448
|
const ROOM_AIR = 9;
|
|
449
|
+
/** Tall enough for two title lines over the time (.tw-ev--tall), in the
|
|
450
|
+
* CSS's own line boxes: title 15px/1.25, time 14px/1.2. */
|
|
451
|
+
const TWO_LINE_H = BODY_PAD_Y + 2 * 15 * 1.25 + LINE_GAP + 14 * 1.2;
|
|
445
452
|
function blockParts(ev, height) {
|
|
446
453
|
return fitParts([
|
|
447
454
|
{
|
|
@@ -661,6 +668,20 @@ function handleColsClick(e) {
|
|
|
661
668
|
let focusedColMs = $state(null);
|
|
662
669
|
const tabbableColMs = $derived((dayCols.find((d) => d.ms === focusedColMs) ?? dayCols.find((d) => d.isToday) ?? dayCols[0])?.ms);
|
|
663
670
|
const colsCreate = $derived(!!oneventcreate && !readOnly);
|
|
671
|
+
// After Enter creates, the keyboard follows the new block once the host
|
|
672
|
+
// adds it: the first unseen event at that start, within two updates.
|
|
673
|
+
let createFocus = null;
|
|
674
|
+
$effect(() => {
|
|
675
|
+
const list = events;
|
|
676
|
+
untrack(() => {
|
|
677
|
+
if (!createFocus) return;
|
|
678
|
+
const { startMs, known } = createFocus;
|
|
679
|
+
const ev = list.find((e) => !known.has(e.id) && e.start.getTime() === startMs);
|
|
680
|
+
const block = ev && colsEl?.querySelector(`.tw-ev[data-event-id="${CSS.escape(ev.id)}"]`);
|
|
681
|
+
if (block) block.focus();
|
|
682
|
+
if (block || --createFocus.tries <= 0) createFocus = null;
|
|
683
|
+
});
|
|
684
|
+
});
|
|
664
685
|
function onColKeydown(e, i) {
|
|
665
686
|
if (e.target !== e.currentTarget) return;
|
|
666
687
|
const day = dayCols[i];
|
|
@@ -675,6 +696,11 @@ function onColKeydown(e, i) {
|
|
|
675
696
|
const slot = isBlockedAt(day.ms, hour);
|
|
676
697
|
if (slot) return ctx.announce(`${slot.label || L.unavailable}, ${blockedRangeLabel(day.ms, slot.start, slot.end)}`);
|
|
677
698
|
const startMs = atHour(day.ms, hour);
|
|
699
|
+
createFocus = {
|
|
700
|
+
startMs,
|
|
701
|
+
known: new Set(events.map((ev) => ev.id)),
|
|
702
|
+
tries: 2
|
|
703
|
+
};
|
|
678
704
|
oneventcreate({
|
|
679
705
|
start: new Date(startMs),
|
|
680
706
|
end: new Date(startMs + Math.max(60, minDuration ?? 0) * 6e4)
|
|
@@ -989,18 +1015,16 @@ function onWindowKeydown(e) {
|
|
|
989
1015
|
{/if}
|
|
990
1016
|
|
|
991
1017
|
<!-- Grid body -->
|
|
992
|
-
<div class="tw-body" style:height="{gridHeight}px">
|
|
1018
|
+
<div class="tw-body" class:tw-body--unplaced={!placed} style:height="{gridHeight}px">
|
|
993
1019
|
<!-- Time gutter -->
|
|
994
1020
|
<div class="tw-gutter" style:width="{GUTTER_W}px" aria-hidden="true">
|
|
995
1021
|
{#each { length: hourCount } as _, i}
|
|
996
|
-
{
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
style:top="{i * HOUR_H}px">{hNum}<small>{hSuf}</small></span>
|
|
1003
|
-
{/if}
|
|
1022
|
+
{@const [hNum, hSuf] = fmtHParts(startHour + i, locale)}
|
|
1023
|
+
<!-- an hour the now plate would half-cover gives way to it -->
|
|
1024
|
+
<span
|
|
1025
|
+
class="tw-gutter-lb"
|
|
1026
|
+
class:tw-gutter-lb--under={nowY !== null && weekHasToday && Math.abs(i * HOUR_H - nowY) < 18}
|
|
1027
|
+
style:top="{i * HOUR_H}px">{hNum}<small>{hSuf}</small></span>
|
|
1004
1028
|
{/each}
|
|
1005
1029
|
{#if axisGhost}
|
|
1006
1030
|
<!-- The axis reads the drag to the minute, at both edges. -->
|
|
@@ -1095,6 +1119,7 @@ function onWindowKeydown(e) {
|
|
|
1095
1119
|
class:tw-ev--past={p.ev.end.getTime() < clock.tick}
|
|
1096
1120
|
class:tw-ev--short={p.height < 44}
|
|
1097
1121
|
class:tw-ev--compact={p.height < 34}
|
|
1122
|
+
class:tw-ev--tall={p.height >= TWO_LINE_H}
|
|
1098
1123
|
class:tw-ev--on-now={weekHasToday && nowY !== null && p.top < nowY && p.top + p.height > nowY}
|
|
1099
1124
|
style:top="{p.top}px"
|
|
1100
1125
|
style:height="{p.height}px"
|
|
@@ -1378,9 +1403,13 @@ function onWindowKeydown(e) {
|
|
|
1378
1403
|
white-space: nowrap;
|
|
1379
1404
|
overflow: hidden;
|
|
1380
1405
|
text-overflow: ellipsis;
|
|
1406
|
+
unicode-bidi: plaintext;
|
|
1407
|
+
text-align: left;
|
|
1381
1408
|
flex: 1;
|
|
1382
1409
|
min-width: 0;
|
|
1383
1410
|
}
|
|
1411
|
+
/* …but it sits on the calendar's side, by the bar */
|
|
1412
|
+
.tw-ad-title:dir(rtl) { text-align: right; }
|
|
1384
1413
|
.tw-ad-span {
|
|
1385
1414
|
font: 400 12.5px/1 var(--dt-sans, system-ui, sans-serif);
|
|
1386
1415
|
font-variant-numeric: tabular-nums;
|
|
@@ -1432,6 +1461,11 @@ function onWindowKeydown(e) {
|
|
|
1432
1461
|
display: flex;
|
|
1433
1462
|
position: relative;
|
|
1434
1463
|
}
|
|
1464
|
+
.tw-body--unplaced {
|
|
1465
|
+
visibility: hidden;
|
|
1466
|
+
animation: tw-reveal 0s 1.5s forwards;
|
|
1467
|
+
}
|
|
1468
|
+
@keyframes tw-reveal { to { visibility: visible; } }
|
|
1435
1469
|
|
|
1436
1470
|
/* ─── Time gutter ────────────────────────────────── */
|
|
1437
1471
|
.tw-gutter {
|
|
@@ -1469,6 +1503,10 @@ function onWindowKeydown(e) {
|
|
|
1469
1503
|
align-self: center;
|
|
1470
1504
|
background: var(--dt-border-day, rgba(15, 15, 15, 0.3));
|
|
1471
1505
|
}
|
|
1506
|
+
/* The first hour has no room above its line: it hangs below it, the
|
|
1507
|
+
tick still on the line. */
|
|
1508
|
+
.tw-gutter-lb:first-child { transform: none; }
|
|
1509
|
+
.tw-gutter-lb:first-child::after { align-self: flex-start; }
|
|
1472
1510
|
.tw-gutter-lb--under {
|
|
1473
1511
|
visibility: hidden;
|
|
1474
1512
|
}
|
|
@@ -1767,6 +1805,30 @@ function onWindowKeydown(e) {
|
|
|
1767
1805
|
white-space: nowrap;
|
|
1768
1806
|
overflow: hidden;
|
|
1769
1807
|
text-overflow: ellipsis;
|
|
1808
|
+
/* A Latin title in an RTL calendar keeps its own direction, so the
|
|
1809
|
+
ellipsis cuts its end, not its start. */
|
|
1810
|
+
unicode-bidi: plaintext;
|
|
1811
|
+
text-align: left;
|
|
1812
|
+
}
|
|
1813
|
+
/* …but it sits on the calendar's side, by the bar */
|
|
1814
|
+
:is(.tw-ev-title, .tw-ev-loc):dir(rtl) { text-align: right; }
|
|
1815
|
+
/* A block tall enough for two title lines over the time wraps the title
|
|
1816
|
+
to two. The parts flow in a wrapping column, so a room that no longer
|
|
1817
|
+
fits under them moves into a second column the block clips: it gives
|
|
1818
|
+
way whole, before the title is cut, instead of being sliced. */
|
|
1819
|
+
.tw-ev--tall .tw-ev-body {
|
|
1820
|
+
flex-wrap: wrap;
|
|
1821
|
+
column-gap: 16px;
|
|
1822
|
+
}
|
|
1823
|
+
.tw-ev--tall :is(.tw-ev-title, .tw-ev-time, .tw-ev-loc) { width: 100%; }
|
|
1824
|
+
.tw-ev--tall .tw-ev-title {
|
|
1825
|
+
display: -webkit-box;
|
|
1826
|
+
-webkit-box-orient: vertical;
|
|
1827
|
+
-webkit-line-clamp: 2;
|
|
1828
|
+
line-clamp: 2;
|
|
1829
|
+
white-space: normal;
|
|
1830
|
+
overflow-wrap: break-word; /* a word wider than an overlap lane breaks, not clips */
|
|
1831
|
+
hyphens: auto; /* …and hyphenates where the host page declares a lang */
|
|
1770
1832
|
}
|
|
1771
1833
|
|
|
1772
1834
|
.tw-ev-loc {
|
|
@@ -1776,6 +1838,8 @@ function onWindowKeydown(e) {
|
|
|
1776
1838
|
white-space: nowrap;
|
|
1777
1839
|
overflow: hidden;
|
|
1778
1840
|
text-overflow: ellipsis;
|
|
1841
|
+
unicode-bidi: plaintext;
|
|
1842
|
+
text-align: left;
|
|
1779
1843
|
}
|
|
1780
1844
|
|
|
1781
1845
|
.tw-ev-live {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nomideusz/svelte-calendar",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.2",
|
|
4
4
|
"description": "A themeable Svelte 5 calendar: day, week, scrolling-week and month views, agenda lists, touch-first mobile layouts, drag-and-drop, timezones, a headless API and an embeddable web component.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|