panelui-native 0.80.0 → 0.81.0
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/lib/module/components/planner/index.js +446 -32
- package/lib/module/components/planner/index.js.map +1 -1
- package/lib/module/components/planner/planner-entries.js +36 -0
- package/lib/module/components/planner/planner-entries.js.map +1 -1
- package/lib/module/components/planner/planner-grid-navigation.js +34 -2
- package/lib/module/components/planner/planner-grid-navigation.js.map +1 -1
- package/lib/module/components/planner/planner-weeks.js +95 -0
- package/lib/module/components/planner/planner-weeks.js.map +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/date.js +10 -3
- package/lib/module/utils/date.js.map +1 -1
- package/lib/typescript/src/components/planner/index.d.ts +65 -4
- package/lib/typescript/src/components/planner/index.d.ts.map +1 -1
- package/lib/typescript/src/components/planner/planner-entries.d.ts +18 -0
- package/lib/typescript/src/components/planner/planner-entries.d.ts.map +1 -1
- package/lib/typescript/src/components/planner/planner-grid-navigation.d.ts +7 -1
- package/lib/typescript/src/components/planner/planner-grid-navigation.d.ts.map +1 -1
- package/lib/typescript/src/components/planner/planner-weeks.d.ts +50 -0
- package/lib/typescript/src/components/planner/planner-weeks.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/date.d.ts +1 -1
- package/lib/typescript/src/utils/date.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/planner/index.tsx +516 -38
- package/src/components/planner/planner-entries.ts +33 -0
- package/src/components/planner/planner-grid-navigation.ts +37 -2
- package/src/components/planner/planner-weeks.ts +90 -0
- package/src/index.ts +2 -0
- package/src/utils/date.ts +13 -3
|
@@ -153,3 +153,36 @@ export function summariseMonth<T extends PlannerDatedEntry>(
|
|
|
153
153
|
})),
|
|
154
154
|
};
|
|
155
155
|
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Minutes since midnight, or `null` for an entry that lands exactly on it.
|
|
159
|
+
*
|
|
160
|
+
* The grid has only ever read an entry's day, so a date with no time set is
|
|
161
|
+
* midnight — and that is indistinguishable from something genuinely scheduled
|
|
162
|
+
* at 00:00. Reading it as all-day is the right way round: a set of entries
|
|
163
|
+
* built without times renders as a list of things happening that day, which is
|
|
164
|
+
* what it is, rather than as a column of midnights.
|
|
165
|
+
*/
|
|
166
|
+
export function entryTimeMinutes(date: Date): number | null {
|
|
167
|
+
const minutes = date.getHours() * 60 + date.getMinutes();
|
|
168
|
+
return minutes === 0 ? null : minutes;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* All-day first, then by time, keeping the caller's order within each.
|
|
173
|
+
*
|
|
174
|
+
* All-day leads because it frames the rest of the day rather than falling at a
|
|
175
|
+
* point in it, and a list that opens with "All day" reads as a heading for
|
|
176
|
+
* what follows.
|
|
177
|
+
*/
|
|
178
|
+
export function sortByTime<T extends PlannerDatedEntry>(entries: readonly T[]): T[] {
|
|
179
|
+
return entries
|
|
180
|
+
.map((entry, index) => ({ entry, index, at: entryTimeMinutes(entry.date) }))
|
|
181
|
+
.sort((a, b) => {
|
|
182
|
+
if (a.at === null && b.at === null) return a.index - b.index;
|
|
183
|
+
if (a.at === null) return -1;
|
|
184
|
+
if (b.at === null) return 1;
|
|
185
|
+
return a.at === b.at ? a.index - b.index : a.at - b.at;
|
|
186
|
+
})
|
|
187
|
+
.map(({ entry }) => entry);
|
|
188
|
+
}
|
|
@@ -6,40 +6,75 @@ export type PlannerGridNavigationKey =
|
|
|
6
6
|
| 'Home'
|
|
7
7
|
| 'End';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Whether a cell can take focus. Some looks leave the days either side of the
|
|
11
|
+
* month blank, and a blank has nothing to focus — so the caller says which
|
|
12
|
+
* indices are real and movement steps over the rest.
|
|
13
|
+
*/
|
|
14
|
+
export type PlannerGridNavigable = (index: number) => boolean;
|
|
15
|
+
|
|
9
16
|
/** Returns the next visible cell for a bounded, row-major month grid. */
|
|
10
17
|
export function plannerGridTarget(
|
|
11
18
|
key: string,
|
|
12
19
|
index: number,
|
|
13
20
|
count: number,
|
|
14
|
-
columns = 7
|
|
21
|
+
columns = 7,
|
|
22
|
+
navigable?: PlannerGridNavigable
|
|
15
23
|
): number | null {
|
|
16
24
|
if (!Number.isInteger(index) || index < 0 || index >= count || columns < 1) {
|
|
17
25
|
return null;
|
|
18
26
|
}
|
|
19
27
|
|
|
20
28
|
let target: number;
|
|
29
|
+
/*
|
|
30
|
+
* Which way to keep going when the first landing is blank. The arrows carry
|
|
31
|
+
* on away from where you were; Home and End start at the edge of the row and
|
|
32
|
+
* come back inward, because the cell they want is the outermost real one and
|
|
33
|
+
* anything past it is off the row entirely.
|
|
34
|
+
*/
|
|
35
|
+
let step: number;
|
|
21
36
|
switch (key as PlannerGridNavigationKey) {
|
|
22
37
|
case 'ArrowLeft':
|
|
23
38
|
target = index - 1;
|
|
39
|
+
step = -1;
|
|
24
40
|
break;
|
|
25
41
|
case 'ArrowRight':
|
|
26
42
|
target = index + 1;
|
|
43
|
+
step = 1;
|
|
27
44
|
break;
|
|
28
45
|
case 'ArrowUp':
|
|
29
46
|
target = index - columns;
|
|
47
|
+
step = -columns;
|
|
30
48
|
break;
|
|
31
49
|
case 'ArrowDown':
|
|
32
50
|
target = index + columns;
|
|
51
|
+
step = columns;
|
|
33
52
|
break;
|
|
34
53
|
case 'Home':
|
|
35
54
|
target = index - (index % columns);
|
|
55
|
+
step = 1;
|
|
36
56
|
break;
|
|
37
57
|
case 'End':
|
|
38
58
|
target = index + (columns - 1 - (index % columns));
|
|
59
|
+
step = -1;
|
|
39
60
|
break;
|
|
40
61
|
default:
|
|
41
62
|
return null;
|
|
42
63
|
}
|
|
43
64
|
|
|
44
|
-
|
|
65
|
+
if (target < 0 || target >= count) return index;
|
|
66
|
+
if (!navigable) return target;
|
|
67
|
+
|
|
68
|
+
/*
|
|
69
|
+
* Stepping stops at the cell you started from rather than running past it.
|
|
70
|
+
* That covers both directions at once: an arrow that finds nothing before
|
|
71
|
+
* the grid ends stays put, and Home or End on a row whose edge is blank
|
|
72
|
+
* walks in until it reaches a real day or arrives back where it began.
|
|
73
|
+
*/
|
|
74
|
+
while (target !== index && !navigable(target)) {
|
|
75
|
+
target += step;
|
|
76
|
+
if (target < 0 || target >= count) return index;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return target;
|
|
45
80
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The arithmetic behind a calendar that scrolls instead of paging: which weeks
|
|
3
|
+
* are reachable, where a week starts, and which month a week belongs to.
|
|
4
|
+
*
|
|
5
|
+
* Nothing here imports anything, for the same reason as `planner-entries`: the
|
|
6
|
+
* tests run this file through `node --test` with type stripping rather than a
|
|
7
|
+
* bundler, so an extensionless import of a sibling does not resolve.
|
|
8
|
+
*
|
|
9
|
+
* A week is seven days whatever calendar is in force, so all of this is plain
|
|
10
|
+
* day arithmetic. Naming the month a week falls in is not, and is left to the
|
|
11
|
+
* caller, which has the calendar system.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
15
|
+
|
|
16
|
+
/** Local midnight, so two dates on the same day compare equal. */
|
|
17
|
+
function atMidnight(date: Date): Date {
|
|
18
|
+
const copy = new Date(date);
|
|
19
|
+
copy.setHours(0, 0, 0, 0);
|
|
20
|
+
return copy;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Days added by the calendar rather than by milliseconds.
|
|
25
|
+
*
|
|
26
|
+
* A day is not always 24 hours — the ones a daylight-saving change falls on are
|
|
27
|
+
* 23 or 25 — so stepping by `DAY_MS` drifts across a transition and lands a
|
|
28
|
+
* week row an hour before or after midnight, which then reads as the wrong day.
|
|
29
|
+
*/
|
|
30
|
+
export function addDaysLocal(date: Date, days: number): Date {
|
|
31
|
+
const copy = new Date(date);
|
|
32
|
+
copy.setDate(copy.getDate() + days);
|
|
33
|
+
return atMidnight(copy);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The first day of the week `date` falls in, for a week starting on `weekStartsOn`. */
|
|
37
|
+
export function startOfWeek(date: Date, weekStartsOn: number): Date {
|
|
38
|
+
const start = ((weekStartsOn % 7) + 7) % 7;
|
|
39
|
+
const midnight = atMidnight(date);
|
|
40
|
+
const shift = (midnight.getDay() - start + 7) % 7;
|
|
41
|
+
return addDaysLocal(midnight, -shift);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The day a week is named after: its midpoint.
|
|
46
|
+
*
|
|
47
|
+
* A week straddling a month boundary belongs to whichever month holds most of
|
|
48
|
+
* it, and the fourth day is the cheapest way to ask that — it is on the heavier
|
|
49
|
+
* side by definition. Naming the week after its first day would put the last
|
|
50
|
+
* week of January under December for as little as one day's overlap.
|
|
51
|
+
*/
|
|
52
|
+
export function weekAnchor(weekStart: Date): Date {
|
|
53
|
+
return addDaysLocal(weekStart, 3);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The seven days of a week, in order. */
|
|
57
|
+
export function weekDays(weekStart: Date): Date[] {
|
|
58
|
+
return Array.from({ length: 7 }, (_unused, day) => addDaysLocal(weekStart, day));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Every week start from `weeksBefore` before the anchor's week to `weeksAfter`
|
|
63
|
+
* after it, in order.
|
|
64
|
+
*
|
|
65
|
+
* A bounded range rather than an endless one: a scroller needs to know how tall
|
|
66
|
+
* it is to place a scrollbar and to jump to a month without rendering its way
|
|
67
|
+
* there, and neither is possible over an infinite list.
|
|
68
|
+
*/
|
|
69
|
+
export function weekRange(anchor: Date, weekStartsOn: number, weeksBefore: number, weeksAfter: number): Date[] {
|
|
70
|
+
const first = startOfWeek(anchor, weekStartsOn);
|
|
71
|
+
const before = Math.max(0, Math.trunc(weeksBefore));
|
|
72
|
+
const after = Math.max(0, Math.trunc(weeksAfter));
|
|
73
|
+
return Array.from({ length: before + after + 1 }, (_unused, step) =>
|
|
74
|
+
addDaysLocal(first, (step - before) * 7)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Where `date`'s week sits in a range, or `-1`.
|
|
80
|
+
*
|
|
81
|
+
* Computed from the span rather than searched for, so jumping to a month costs
|
|
82
|
+
* the same whether it is one screen away or a year.
|
|
83
|
+
*/
|
|
84
|
+
export function weekIndex(weeks: readonly Date[], date: Date, weekStartsOn: number): number {
|
|
85
|
+
const first = weeks[0];
|
|
86
|
+
if (!first) return -1;
|
|
87
|
+
const target = startOfWeek(date, weekStartsOn);
|
|
88
|
+
const index = Math.round((target.getTime() - first.getTime()) / (7 * DAY_MS));
|
|
89
|
+
return index >= 0 && index < weeks.length ? index : -1;
|
|
90
|
+
}
|
package/src/index.ts
CHANGED
package/src/utils/date.ts
CHANGED
|
@@ -535,13 +535,23 @@ export function monthNames(locale: DateLocale, style: 'long' | 'short' = 'long')
|
|
|
535
535
|
}
|
|
536
536
|
|
|
537
537
|
/** Column headings, rotated so the first one is `weekStartsOn`. */
|
|
538
|
-
export function weekdayNames(
|
|
538
|
+
export function weekdayNames(
|
|
539
|
+
locale: DateLocale,
|
|
540
|
+
weekStartsOn: number,
|
|
541
|
+
/*
|
|
542
|
+
* `narrow` is a single letter in most locales, and several of them repeat —
|
|
543
|
+
* English has T twice and S twice. That is only safe where the column it
|
|
544
|
+
* heads is already unambiguous by position, which is why it is a caller's
|
|
545
|
+
* choice rather than the default.
|
|
546
|
+
*/
|
|
547
|
+
width: 'short' | 'narrow' = 'short'
|
|
548
|
+
): string[] {
|
|
539
549
|
const first = normalizeWeekStart(weekStartsOn);
|
|
540
550
|
// 2021-08-01 was a Sunday, so adding the index lands on each weekday in turn.
|
|
541
551
|
return Array.from({ length: 7 }, (_unused, day) => {
|
|
542
552
|
const index = (day + first) % 7;
|
|
543
|
-
const formatted = formatGregory(new Date(2021, 7, 1 + index), locale, { weekday:
|
|
544
|
-
return formatted ?? WEEKDAYS_EN[index]
|
|
553
|
+
const formatted = formatGregory(new Date(2021, 7, 1 + index), locale, { weekday: width });
|
|
554
|
+
return formatted ?? (width === 'narrow' ? WEEKDAYS_EN[index]![0]! : WEEKDAYS_EN[index]!);
|
|
545
555
|
});
|
|
546
556
|
}
|
|
547
557
|
|