@dowel-ui/react 0.4.0 → 0.7.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/README.md +9 -5
- package/dist/blocks/admin-dashboard/meta.js +31 -0
- package/dist/blocks/admin-dashboard/meta.js.map +1 -0
- package/dist/blocks/agent-console/meta.js +27 -0
- package/dist/blocks/agent-console/meta.js.map +1 -0
- package/dist/blocks/ai-dashboard/meta.js +25 -0
- package/dist/blocks/ai-dashboard/meta.js.map +1 -0
- package/dist/blocks/ai-workspace/meta.js +33 -0
- package/dist/blocks/ai-workspace/meta.js.map +1 -0
- package/dist/blocks/analytics/meta.js +24 -0
- package/dist/blocks/analytics/meta.js.map +1 -0
- package/dist/blocks/billing/meta.js +25 -0
- package/dist/blocks/billing/meta.js.map +1 -0
- package/dist/blocks/command-center/meta.js +28 -0
- package/dist/blocks/command-center/meta.js.map +1 -0
- package/dist/blocks/crm/meta.js +32 -0
- package/dist/blocks/crm/meta.js.map +1 -0
- package/dist/blocks/onboarding/meta.js +23 -0
- package/dist/blocks/onboarding/meta.js.map +1 -0
- package/dist/components/accordion/accordion.js +1 -1
- package/dist/components/accordion/accordion.js.map +1 -1
- package/dist/components/activity-feed/activity-feed.js +1 -1
- package/dist/components/activity-feed/activity-feed.js.map +1 -1
- package/dist/components/ai-agent-plan/ai-agent-plan.js +1 -1
- package/dist/components/ai-agent-plan/ai-agent-plan.js.map +1 -1
- package/dist/components/ai-extraction-review/ai-extraction-review.d.ts +52 -0
- package/dist/components/ai-extraction-review/ai-extraction-review.d.ts.map +1 -0
- package/dist/components/ai-extraction-review/ai-extraction-review.js +249 -0
- package/dist/components/ai-extraction-review/ai-extraction-review.js.map +1 -0
- package/dist/components/ai-extraction-review/extraction-model.d.ts +94 -0
- package/dist/components/ai-extraction-review/extraction-model.d.ts.map +1 -0
- package/dist/components/ai-extraction-review/extraction-model.js +91 -0
- package/dist/components/ai-extraction-review/extraction-model.js.map +1 -0
- package/dist/components/ai-extraction-review/index.d.ts +3 -0
- package/dist/components/ai-extraction-review/index.js +3 -0
- package/dist/components/ai-extraction-review/meta.js +17 -0
- package/dist/components/ai-extraction-review/meta.js.map +1 -0
- package/dist/components/ai-prompt-input/ai-prompt-input.js +1 -1
- package/dist/components/ai-prompt-input/ai-prompt-input.js.map +1 -1
- package/dist/components/ai-reasoning/ai-reasoning.js +1 -1
- package/dist/components/ai-reasoning/ai-reasoning.js.map +1 -1
- package/dist/components/ai-response/ai-response.js +2 -2
- package/dist/components/ai-response/ai-response.js.map +1 -1
- package/dist/components/ai-suggested-value/ai-suggested-value.d.ts +72 -0
- package/dist/components/ai-suggested-value/ai-suggested-value.d.ts.map +1 -0
- package/dist/components/ai-suggested-value/ai-suggested-value.js +126 -0
- package/dist/components/ai-suggested-value/ai-suggested-value.js.map +1 -0
- package/dist/components/ai-suggested-value/index.d.ts +2 -0
- package/dist/components/ai-suggested-value/index.js +2 -0
- package/dist/components/ai-suggested-value/meta.js +17 -0
- package/dist/components/ai-suggested-value/meta.js.map +1 -0
- package/dist/components/ai-tool/ai-tool.js +2 -2
- package/dist/components/ai-tool/ai-tool.js.map +1 -1
- package/dist/components/breadcrumb/breadcrumb.d.ts +52 -0
- package/dist/components/breadcrumb/breadcrumb.d.ts.map +1 -0
- package/dist/components/breadcrumb/breadcrumb.js +141 -0
- package/dist/components/breadcrumb/breadcrumb.js.map +1 -0
- package/dist/components/breadcrumb/index.d.ts +2 -0
- package/dist/components/breadcrumb/index.js +2 -0
- package/dist/components/breadcrumb/meta.js +17 -0
- package/dist/components/breadcrumb/meta.js.map +1 -0
- package/dist/components/calendar/calendar.js +2 -2
- package/dist/components/calendar/calendar.js.map +1 -1
- package/dist/components/code-block/code-block.js +1 -1
- package/dist/components/code-block/code-block.js.map +1 -1
- package/dist/components/collapsible/collapsible.d.ts +30 -0
- package/dist/components/collapsible/collapsible.d.ts.map +1 -0
- package/dist/components/collapsible/collapsible.js +38 -0
- package/dist/components/collapsible/collapsible.js.map +1 -0
- package/dist/components/collapsible/index.d.ts +2 -0
- package/dist/components/collapsible/index.js +2 -0
- package/dist/components/collapsible/meta.js +17 -0
- package/dist/components/collapsible/meta.js.map +1 -0
- package/dist/components/combobox/combobox.js +2 -2
- package/dist/components/combobox/combobox.js.map +1 -1
- package/dist/components/command/command.js +1 -1
- package/dist/components/command/command.js.map +1 -1
- package/dist/components/confirm-typed/confirm-typed.d.ts +47 -0
- package/dist/components/confirm-typed/confirm-typed.d.ts.map +1 -0
- package/dist/components/confirm-typed/confirm-typed.js +106 -0
- package/dist/components/confirm-typed/confirm-typed.js.map +1 -0
- package/dist/components/confirm-typed/index.d.ts +2 -0
- package/dist/components/confirm-typed/index.js +2 -0
- package/dist/components/confirm-typed/meta.js +17 -0
- package/dist/components/confirm-typed/meta.js.map +1 -0
- package/dist/components/cron-editor/cron-editor.d.ts +36 -0
- package/dist/components/cron-editor/cron-editor.d.ts.map +1 -0
- package/dist/components/cron-editor/cron-editor.js +493 -0
- package/dist/components/cron-editor/cron-editor.js.map +1 -0
- package/dist/components/cron-editor/cron-expression.d.ts +82 -0
- package/dist/components/cron-editor/cron-expression.d.ts.map +1 -0
- package/dist/components/cron-editor/cron-expression.js +352 -0
- package/dist/components/cron-editor/cron-expression.js.map +1 -0
- package/dist/components/cron-editor/index.d.ts +3 -0
- package/dist/components/cron-editor/index.js +3 -0
- package/dist/components/cron-editor/meta.js +17 -0
- package/dist/components/cron-editor/meta.js.map +1 -0
- package/dist/components/data-table/data-table.d.ts.map +1 -1
- package/dist/components/data-table/data-table.js +4 -1
- package/dist/components/data-table/data-table.js.map +1 -1
- package/dist/components/dialog/dialog.js +2 -2
- package/dist/components/dialog/dialog.js.map +1 -1
- package/dist/components/diff-viewer/diff-viewer.js +4 -4
- package/dist/components/diff-viewer/diff-viewer.js.map +1 -1
- package/dist/components/direction/direction.d.ts +35 -0
- package/dist/components/direction/direction.d.ts.map +1 -0
- package/dist/components/direction/direction.js +14 -0
- package/dist/components/direction/direction.js.map +1 -0
- package/dist/components/direction/index.d.ts +2 -0
- package/dist/components/direction/index.js +2 -0
- package/dist/components/direction/meta.js +17 -0
- package/dist/components/direction/meta.js.map +1 -0
- package/dist/components/dns-record/dns-record.d.ts +61 -0
- package/dist/components/dns-record/dns-record.d.ts.map +1 -0
- package/dist/components/dns-record/dns-record.js +227 -0
- package/dist/components/dns-record/dns-record.js.map +1 -0
- package/dist/components/dns-record/index.d.ts +2 -0
- package/dist/components/dns-record/index.js +2 -0
- package/dist/components/dns-record/meta.js +17 -0
- package/dist/components/dns-record/meta.js.map +1 -0
- package/dist/components/drawer/drawer.js +1 -1
- package/dist/components/drawer/drawer.js.map +1 -1
- package/dist/components/dropdown-menu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/dropdown-menu/dropdown-menu.js +9 -8
- package/dist/components/dropdown-menu/dropdown-menu.js.map +1 -1
- package/dist/components/input/input.js +1 -1
- package/dist/components/input/input.js.map +1 -1
- package/dist/components/log-viewer/log-viewer.js +2 -2
- package/dist/components/log-viewer/log-viewer.js.map +1 -1
- package/dist/components/meter/meter.js +1 -1
- package/dist/components/meter/meter.js.map +1 -1
- package/dist/components/pagination/pagination.d.ts.map +1 -1
- package/dist/components/pagination/pagination.js +3 -2
- package/dist/components/pagination/pagination.js.map +1 -1
- package/dist/components/permission-matrix/index.d.ts +3 -0
- package/dist/components/permission-matrix/index.js +3 -0
- package/dist/components/permission-matrix/meta.js +17 -0
- package/dist/components/permission-matrix/meta.js.map +1 -0
- package/dist/components/permission-matrix/permission-matrix.d.ts +46 -0
- package/dist/components/permission-matrix/permission-matrix.d.ts.map +1 -0
- package/dist/components/permission-matrix/permission-matrix.js +199 -0
- package/dist/components/permission-matrix/permission-matrix.js.map +1 -0
- package/dist/components/permission-matrix/permission-model.d.ts +53 -0
- package/dist/components/permission-matrix/permission-model.d.ts.map +1 -0
- package/dist/components/permission-matrix/permission-model.js +64 -0
- package/dist/components/permission-matrix/permission-model.js.map +1 -0
- package/dist/components/record-diff/record-diff.js +4 -4
- package/dist/components/record-diff/record-diff.js.map +1 -1
- package/dist/components/secret-field/index.d.ts +2 -0
- package/dist/components/secret-field/index.js +2 -0
- package/dist/components/secret-field/meta.js +17 -0
- package/dist/components/secret-field/meta.js.map +1 -0
- package/dist/components/secret-field/secret-field.d.ts +58 -0
- package/dist/components/secret-field/secret-field.d.ts.map +1 -0
- package/dist/components/secret-field/secret-field.js +168 -0
- package/dist/components/secret-field/secret-field.js.map +1 -0
- package/dist/components/select/select.js +2 -2
- package/dist/components/select/select.js.map +1 -1
- package/dist/components/session-expiry/index.d.ts +2 -0
- package/dist/components/session-expiry/index.js +2 -0
- package/dist/components/session-expiry/meta.js +17 -0
- package/dist/components/session-expiry/meta.js.map +1 -0
- package/dist/components/session-expiry/session-expiry.d.ts +64 -0
- package/dist/components/session-expiry/session-expiry.d.ts.map +1 -0
- package/dist/components/session-expiry/session-expiry.js +185 -0
- package/dist/components/session-expiry/session-expiry.js.map +1 -0
- package/dist/components/sheet/sheet.d.ts.map +1 -1
- package/dist/components/sheet/sheet.js +2 -2
- package/dist/components/sheet/sheet.js.map +1 -1
- package/dist/components/shortcut-recorder/index.d.ts +3 -0
- package/dist/components/shortcut-recorder/index.js +3 -0
- package/dist/components/shortcut-recorder/meta.js +17 -0
- package/dist/components/shortcut-recorder/meta.js.map +1 -0
- package/dist/components/shortcut-recorder/shortcut-model.d.ts +67 -0
- package/dist/components/shortcut-recorder/shortcut-model.d.ts.map +1 -0
- package/dist/components/shortcut-recorder/shortcut-model.js +168 -0
- package/dist/components/shortcut-recorder/shortcut-model.js.map +1 -0
- package/dist/components/shortcut-recorder/shortcut-recorder.d.ts +47 -0
- package/dist/components/shortcut-recorder/shortcut-recorder.d.ts.map +1 -0
- package/dist/components/shortcut-recorder/shortcut-recorder.js +212 -0
- package/dist/components/shortcut-recorder/shortcut-recorder.js.map +1 -0
- package/dist/components/sidebar/index.d.ts +2 -0
- package/dist/components/sidebar/index.js +2 -0
- package/dist/components/sidebar/meta.js +17 -0
- package/dist/components/sidebar/meta.js.map +1 -0
- package/dist/components/sidebar/sidebar.d.ts +69 -0
- package/dist/components/sidebar/sidebar.d.ts.map +1 -0
- package/dist/components/sidebar/sidebar.js +256 -0
- package/dist/components/sidebar/sidebar.js.map +1 -0
- package/dist/components/sync-status/index.d.ts +2 -0
- package/dist/components/sync-status/index.js +2 -0
- package/dist/components/sync-status/meta.js +17 -0
- package/dist/components/sync-status/meta.js.map +1 -0
- package/dist/components/sync-status/sync-status.d.ts +65 -0
- package/dist/components/sync-status/sync-status.d.ts.map +1 -0
- package/dist/components/sync-status/sync-status.js +103 -0
- package/dist/components/sync-status/sync-status.js.map +1 -0
- package/dist/components/table/table.js +2 -2
- package/dist/components/table/table.js.map +1 -1
- package/dist/components/tags-input/tags-input.js +1 -1
- package/dist/components/tags-input/tags-input.js.map +1 -1
- package/dist/components/textarea/index.d.ts +2 -0
- package/dist/components/textarea/index.js +2 -0
- package/dist/components/textarea/meta.js +17 -0
- package/dist/components/textarea/meta.js.map +1 -0
- package/dist/components/textarea/textarea.d.ts +43 -0
- package/dist/components/textarea/textarea.d.ts.map +1 -0
- package/dist/components/textarea/textarea.js +119 -0
- package/dist/components/textarea/textarea.js.map +1 -0
- package/dist/components/time-range-picker/time-range-picker.js +3 -3
- package/dist/components/time-range-picker/time-range-picker.js.map +1 -1
- package/dist/components/toast/toast.d.ts.map +1 -1
- package/dist/components/toast/toast.js +2 -2
- package/dist/components/toast/toast.js.map +1 -1
- package/dist/index.d.ts +36 -2
- package/dist/index.js +22 -3
- package/dist/lib/styles.d.ts +16 -1
- package/dist/lib/styles.d.ts.map +1 -1
- package/dist/lib/styles.js +16 -1
- package/dist/lib/styles.js.map +1 -1
- package/dist/registry/blocks.d.ts.map +1 -1
- package/dist/registry/blocks.js +27 -9
- package/dist/registry/blocks.js.map +1 -1
- package/dist/registry/components.d.ts.map +1 -1
- package/dist/registry/components.js +84 -54
- package/dist/registry/components.js.map +1 -1
- package/dist/registry/schema.d.ts +8 -0
- package/dist/registry/schema.d.ts.map +1 -1
- package/dist/registry/schema.js.map +1 -1
- package/package.json +5 -4
- package/src/components/accordion/accordion.tsx +1 -1
- package/src/components/activity-feed/activity-feed.tsx +1 -1
- package/src/components/ai-agent-plan/ai-agent-plan.tsx +0 -0
- package/src/components/ai-extraction-review/ai-extraction-review.tsx +492 -0
- package/src/components/ai-extraction-review/extraction-model.ts +159 -0
- package/src/components/ai-extraction-review/index.ts +13 -0
- package/src/components/ai-extraction-review/meta.ts +24 -0
- package/src/components/ai-prompt-input/ai-prompt-input.tsx +1 -1
- package/src/components/ai-reasoning/ai-reasoning.tsx +1 -1
- package/src/components/ai-response/ai-response.tsx +2 -2
- package/src/components/ai-suggested-value/ai-suggested-value.tsx +295 -0
- package/src/components/ai-suggested-value/index.ts +6 -0
- package/src/components/ai-suggested-value/meta.ts +24 -0
- package/src/components/ai-tool/ai-tool.tsx +2 -2
- package/src/components/breadcrumb/breadcrumb.tsx +157 -0
- package/src/components/breadcrumb/index.ts +10 -0
- package/src/components/breadcrumb/meta.ts +19 -0
- package/src/components/calendar/calendar.tsx +2 -2
- package/src/components/code-block/code-block.tsx +1 -1
- package/src/components/collapsible/collapsible.tsx +63 -0
- package/src/components/collapsible/index.ts +6 -0
- package/src/components/collapsible/meta.ts +16 -0
- package/src/components/combobox/combobox.tsx +2 -2
- package/src/components/command/command.tsx +1 -1
- package/src/components/confirm-typed/confirm-typed.tsx +191 -0
- package/src/components/confirm-typed/index.ts +1 -0
- package/src/components/confirm-typed/meta.ts +22 -0
- package/src/components/cron-editor/cron-editor.tsx +676 -0
- package/src/components/cron-editor/cron-expression.ts +552 -0
- package/src/components/cron-editor/index.ts +23 -0
- package/src/components/cron-editor/meta.ts +23 -0
- package/src/components/data-table/data-table.tsx +14 -3
- package/src/components/dialog/dialog.tsx +2 -2
- package/src/components/diff-viewer/diff-viewer.tsx +4 -4
- package/src/components/direction/direction.tsx +38 -0
- package/src/components/direction/index.ts +1 -0
- package/src/components/direction/meta.ts +19 -0
- package/src/components/dns-record/dns-record.tsx +315 -0
- package/src/components/dns-record/index.ts +7 -0
- package/src/components/dns-record/meta.ts +21 -0
- package/src/components/drawer/drawer.tsx +1 -1
- package/src/components/dropdown-menu/dropdown-menu.tsx +14 -8
- package/src/components/input/input.tsx +1 -1
- package/src/components/log-viewer/log-viewer.tsx +2 -2
- package/src/components/meter/meter.tsx +1 -1
- package/src/components/pagination/pagination.tsx +13 -2
- package/src/components/permission-matrix/index.ts +14 -0
- package/src/components/permission-matrix/meta.ts +23 -0
- package/src/components/permission-matrix/permission-matrix.tsx +368 -0
- package/src/components/permission-matrix/permission-model.ts +120 -0
- package/src/components/record-diff/record-diff.tsx +4 -4
- package/src/components/secret-field/index.ts +6 -0
- package/src/components/secret-field/meta.ts +21 -0
- package/src/components/secret-field/secret-field.tsx +284 -0
- package/src/components/select/select.tsx +2 -2
- package/src/components/session-expiry/index.ts +9 -0
- package/src/components/session-expiry/meta.ts +23 -0
- package/src/components/session-expiry/session-expiry.tsx +321 -0
- package/src/components/sheet/sheet.tsx +8 -4
- package/src/components/shortcut-recorder/index.ts +19 -0
- package/src/components/shortcut-recorder/meta.ts +22 -0
- package/src/components/shortcut-recorder/shortcut-model.ts +204 -0
- package/src/components/shortcut-recorder/shortcut-recorder.tsx +336 -0
- package/src/components/sidebar/index.ts +19 -0
- package/src/components/sidebar/meta.ts +25 -0
- package/src/components/sidebar/sidebar.tsx +374 -0
- package/src/components/sync-status/index.ts +9 -0
- package/src/components/sync-status/meta.ts +21 -0
- package/src/components/sync-status/sync-status.tsx +217 -0
- package/src/components/table/table.tsx +3 -3
- package/src/components/tags-input/tags-input.tsx +1 -1
- package/src/components/textarea/index.ts +1 -0
- package/src/components/textarea/meta.ts +21 -0
- package/src/components/textarea/textarea.tsx +210 -0
- package/src/components/time-range-picker/time-range-picker.tsx +3 -3
- package/src/components/toast/toast.tsx +14 -6
- package/src/index.ts +15 -0
- package/src/lib/styles.ts +16 -0
- package/src/registry/blocks.ts +18 -0
- package/src/registry/components.ts +30 -0
- package/src/registry/schema.ts +8 -0
- package/src/blocks/admin-users/admin-users.tsx +0 -251
- package/src/blocks/admin-users/index.ts +0 -1
- package/src/blocks/admin-users/meta.ts +0 -27
- package/src/blocks/ai-chat/ai-chat.tsx +0 -280
- package/src/blocks/ai-chat/index.ts +0 -7
- package/src/blocks/ai-chat/meta.ts +0 -32
- package/src/blocks/dashboard/dashboard.tsx +0 -192
- package/src/blocks/dashboard/index.ts +0 -6
- package/src/blocks/dashboard/meta.ts +0 -18
- package/src/blocks/forgot-password/forgot-password.tsx +0 -158
- package/src/blocks/forgot-password/index.ts +0 -1
- package/src/blocks/forgot-password/meta.ts +0 -18
- package/src/blocks/login/index.ts +0 -1
- package/src/blocks/login/login.tsx +0 -178
- package/src/blocks/login/meta.ts +0 -18
- package/src/blocks/pricing/index.ts +0 -1
- package/src/blocks/pricing/meta.ts +0 -18
- package/src/blocks/pricing/pricing.tsx +0 -176
- package/src/blocks/settings/index.ts +0 -6
- package/src/blocks/settings/meta.ts +0 -29
- package/src/blocks/settings/settings.tsx +0 -317
- package/src/blocks/signup/index.ts +0 -1
- package/src/blocks/signup/meta.ts +0 -18
- package/src/blocks/signup/signup.tsx +0 -240
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
//#region src/components/cron-editor/cron-expression.ts
|
|
2
|
+
var CronExpressionError = class extends Error {
|
|
3
|
+
field;
|
|
4
|
+
constructor(message, field) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.field = field;
|
|
7
|
+
this.name = "CronExpressionError";
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
const FIELDS = [
|
|
11
|
+
"minute",
|
|
12
|
+
"hour",
|
|
13
|
+
"dayOfMonth",
|
|
14
|
+
"month",
|
|
15
|
+
"dayOfWeek"
|
|
16
|
+
];
|
|
17
|
+
const RANGES = {
|
|
18
|
+
minute: {
|
|
19
|
+
min: 0,
|
|
20
|
+
max: 59,
|
|
21
|
+
label: "Minute"
|
|
22
|
+
},
|
|
23
|
+
hour: {
|
|
24
|
+
min: 0,
|
|
25
|
+
max: 23,
|
|
26
|
+
label: "Hour"
|
|
27
|
+
},
|
|
28
|
+
dayOfMonth: {
|
|
29
|
+
min: 1,
|
|
30
|
+
max: 31,
|
|
31
|
+
label: "Day of month"
|
|
32
|
+
},
|
|
33
|
+
month: {
|
|
34
|
+
min: 1,
|
|
35
|
+
max: 12,
|
|
36
|
+
label: "Month"
|
|
37
|
+
},
|
|
38
|
+
dayOfWeek: {
|
|
39
|
+
min: 0,
|
|
40
|
+
max: 7,
|
|
41
|
+
label: "Day of week"
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const MONTH_NAMES = [
|
|
45
|
+
"JAN",
|
|
46
|
+
"FEB",
|
|
47
|
+
"MAR",
|
|
48
|
+
"APR",
|
|
49
|
+
"MAY",
|
|
50
|
+
"JUN",
|
|
51
|
+
"JUL",
|
|
52
|
+
"AUG",
|
|
53
|
+
"SEP",
|
|
54
|
+
"OCT",
|
|
55
|
+
"NOV",
|
|
56
|
+
"DEC"
|
|
57
|
+
];
|
|
58
|
+
const DAY_NAMES = [
|
|
59
|
+
"SUN",
|
|
60
|
+
"MON",
|
|
61
|
+
"TUE",
|
|
62
|
+
"WED",
|
|
63
|
+
"THU",
|
|
64
|
+
"FRI",
|
|
65
|
+
"SAT"
|
|
66
|
+
];
|
|
67
|
+
const CRON_SHORTCUTS = {
|
|
68
|
+
"@yearly": "0 0 1 1 *",
|
|
69
|
+
"@annually": "0 0 1 1 *",
|
|
70
|
+
"@monthly": "0 0 1 * *",
|
|
71
|
+
"@weekly": "0 0 * * 0",
|
|
72
|
+
"@daily": "0 0 * * *",
|
|
73
|
+
"@midnight": "0 0 * * *",
|
|
74
|
+
"@hourly": "0 * * * *"
|
|
75
|
+
};
|
|
76
|
+
function nameToNumber(token, field) {
|
|
77
|
+
const upper = token.toUpperCase();
|
|
78
|
+
if (field === "month") {
|
|
79
|
+
const index = MONTH_NAMES.indexOf(upper);
|
|
80
|
+
return index === -1 ? null : index + 1;
|
|
81
|
+
}
|
|
82
|
+
if (field === "dayOfWeek") {
|
|
83
|
+
const index = DAY_NAMES.indexOf(upper);
|
|
84
|
+
return index === -1 ? null : index;
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
function parseNumber(token, field) {
|
|
89
|
+
const { min, max, label } = RANGES[field];
|
|
90
|
+
const value = nameToNumber(token, field) ?? (/^\d+$/.test(token) ? Number(token) : NaN);
|
|
91
|
+
if (Number.isNaN(value)) throw new CronExpressionError(`${label}: "${token}" is not a number${field === "month" || field === "dayOfWeek" ? " or a name" : ""}.`, field);
|
|
92
|
+
if (value < min || value > max) throw new CronExpressionError(`${label} must be ${String(min)}–${String(max)}, not ${String(value)}.`, field);
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
95
|
+
/** One field's set of values, in order and without duplicates. */
|
|
96
|
+
function parseField(text, field) {
|
|
97
|
+
const { min, max, label } = RANGES[field];
|
|
98
|
+
const top = field === "dayOfWeek" ? 6 : max;
|
|
99
|
+
const values = /* @__PURE__ */ new Set();
|
|
100
|
+
let restricted = false;
|
|
101
|
+
for (const part of text.split(",")) {
|
|
102
|
+
if (part === "") throw new CronExpressionError(`${label}: empty item in "${text}".`, field);
|
|
103
|
+
const [rangeText, stepText, ...extra] = part.split("/");
|
|
104
|
+
if (extra.length > 0 || stepText === "") throw new CronExpressionError(`${label}: "${part}" has a malformed step.`, field);
|
|
105
|
+
const step = stepText === void 0 ? 1 : Number(stepText);
|
|
106
|
+
if (stepText !== void 0 && (!/^\d+$/.test(stepText) || step < 1)) throw new CronExpressionError(`${label}: step must be a whole number of 1 or more, not "${stepText}".`, field);
|
|
107
|
+
let start;
|
|
108
|
+
let end;
|
|
109
|
+
if (rangeText === "*") {
|
|
110
|
+
start = min;
|
|
111
|
+
end = top;
|
|
112
|
+
if (stepText !== void 0) restricted = true;
|
|
113
|
+
} else if (rangeText === void 0) throw new CronExpressionError(`${label}: "${part}" is empty.`, field);
|
|
114
|
+
else {
|
|
115
|
+
restricted = true;
|
|
116
|
+
const [startText, endText, ...more] = rangeText.split("-");
|
|
117
|
+
if (more.length > 0 || startText === void 0 || startText === "") throw new CronExpressionError(`${label}: "${rangeText}" is not a range.`, field);
|
|
118
|
+
start = parseNumber(startText, field);
|
|
119
|
+
if (endText === void 0) end = stepText === void 0 ? start : top;
|
|
120
|
+
else {
|
|
121
|
+
if (endText === "") throw new CronExpressionError(`${label}: "${rangeText}" is missing its end.`, field);
|
|
122
|
+
end = parseNumber(endText, field);
|
|
123
|
+
if (end < start) throw new CronExpressionError(`${label}: ${String(start)}-${String(end)} runs backwards.`, field);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
for (let value = start; value <= end; value += step) values.add(field === "dayOfWeek" && value === 7 ? 0 : value);
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
values: [...values].sort((a, b) => a - b),
|
|
130
|
+
restricted
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function parseCron(expression) {
|
|
134
|
+
const trimmed = expression.trim();
|
|
135
|
+
if (trimmed === "") throw new CronExpressionError("Enter a schedule.");
|
|
136
|
+
const expanded = trimmed.startsWith("@") ? CRON_SHORTCUTS[trimmed.toLowerCase()] : trimmed;
|
|
137
|
+
if (expanded === void 0) throw new CronExpressionError(`"${trimmed}" is not a shortcut. Try @hourly, @daily, @weekly, @monthly or @yearly.`);
|
|
138
|
+
const parts = expanded.split(/\s+/);
|
|
139
|
+
if (parts.length !== 5) throw new CronExpressionError(`Expected 5 fields — minute, hour, day of month, month, day of week — not ${String(parts.length)}.`);
|
|
140
|
+
const fields = FIELDS.map((field, index) => parseField(parts[index] ?? "", field));
|
|
141
|
+
const at = (index) => fields[index] ?? {
|
|
142
|
+
values: [],
|
|
143
|
+
restricted: false
|
|
144
|
+
};
|
|
145
|
+
const [minute, hour, dayOfMonth, month, dayOfWeek] = [
|
|
146
|
+
at(0),
|
|
147
|
+
at(1),
|
|
148
|
+
at(2),
|
|
149
|
+
at(3),
|
|
150
|
+
at(4)
|
|
151
|
+
];
|
|
152
|
+
return {
|
|
153
|
+
minutes: minute.values,
|
|
154
|
+
hours: hour.values,
|
|
155
|
+
daysOfMonth: dayOfMonth.values,
|
|
156
|
+
months: month.values,
|
|
157
|
+
daysOfWeek: dayOfWeek.values,
|
|
158
|
+
dayOfMonthRestricted: dayOfMonth.restricted,
|
|
159
|
+
dayOfWeekRestricted: dayOfWeek.restricted,
|
|
160
|
+
expression: parts.join(" ")
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function isValidCron(expression) {
|
|
164
|
+
try {
|
|
165
|
+
parseCron(expression);
|
|
166
|
+
return true;
|
|
167
|
+
} catch {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/** "1, 2 and 3", or "1 to 5" when the values are consecutive. */
|
|
172
|
+
function listOf(items, consecutive) {
|
|
173
|
+
if (items.length === 1) return items[0] ?? "";
|
|
174
|
+
if (consecutive && items.length > 2) return `${items[0] ?? ""} to ${items[items.length - 1] ?? ""}`;
|
|
175
|
+
return `${items.slice(0, -1).join(", ")} and ${items[items.length - 1] ?? ""}`;
|
|
176
|
+
}
|
|
177
|
+
function isConsecutive(values) {
|
|
178
|
+
return values.every((value, index) => index === 0 || value === (values[index - 1] ?? 0) + 1);
|
|
179
|
+
}
|
|
180
|
+
/** The step a field was written with, when it was written as a star over a number. */
|
|
181
|
+
function stepOf(text) {
|
|
182
|
+
const match = /^\*\/(\d+)$/.exec(text);
|
|
183
|
+
return match ? Number(match[1]) : null;
|
|
184
|
+
}
|
|
185
|
+
function ordinal(n) {
|
|
186
|
+
const rem10 = n % 10;
|
|
187
|
+
const rem100 = n % 100;
|
|
188
|
+
return `${String(n)}${rem10 === 1 && rem100 !== 11 ? "st" : rem10 === 2 && rem100 !== 12 ? "nd" : rem10 === 3 && rem100 !== 13 ? "rd" : "th"}`;
|
|
189
|
+
}
|
|
190
|
+
function formatTime(hour, minute, locale) {
|
|
191
|
+
return new Intl.DateTimeFormat(locale, {
|
|
192
|
+
hour: "2-digit",
|
|
193
|
+
minute: "2-digit",
|
|
194
|
+
timeZone: "UTC"
|
|
195
|
+
}).format(Date.UTC(2026, 0, 1, hour, minute));
|
|
196
|
+
}
|
|
197
|
+
function dayName(index, locale) {
|
|
198
|
+
return new Intl.DateTimeFormat(locale, { weekday: "long" }).format(new Date(2026, 0, 4 + index));
|
|
199
|
+
}
|
|
200
|
+
function monthName(index, locale) {
|
|
201
|
+
return new Intl.DateTimeFormat(locale, { month: "long" }).format(new Date(2026, index - 1, 1));
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* The schedule in a sentence.
|
|
205
|
+
*
|
|
206
|
+
* Every product that has this control renders "Every Monday at 09:00" beside
|
|
207
|
+
* the expression, because `0 9 * * 1` is not something most people can read.
|
|
208
|
+
* Throws for an invalid expression; the editor turns that into the field's
|
|
209
|
+
* error text.
|
|
210
|
+
*/
|
|
211
|
+
function describeCron(expression, options = {}) {
|
|
212
|
+
const { locale } = options;
|
|
213
|
+
const schedule = parseCron(expression);
|
|
214
|
+
const [minuteText = "", hourText = "", dayText = "", monthText = ""] = schedule.expression.split(" ");
|
|
215
|
+
const every = (label, n) => n === 1 ? `every ${label}` : `every ${ordinal(n)} ${label}`;
|
|
216
|
+
let time;
|
|
217
|
+
const minuteStep = stepOf(minuteText);
|
|
218
|
+
const hourStep = stepOf(hourText);
|
|
219
|
+
const allMinutes = minuteText === "*";
|
|
220
|
+
const allHours = hourText === "*";
|
|
221
|
+
if (allMinutes && allHours) time = "Every minute";
|
|
222
|
+
else if (minuteStep !== null && allHours) time = `Every ${minuteStep === 1 ? "minute" : `${String(minuteStep)} minutes`}`;
|
|
223
|
+
else if (allMinutes) time = `Every minute of ${listOf(schedule.hours.map((h) => formatTime(h, 0, locale)), isConsecutive(schedule.hours))}`;
|
|
224
|
+
else if (allHours || hourStep !== null) {
|
|
225
|
+
const hours = hourStep !== null && hourStep > 1 ? every("hour", hourStep) : "every hour";
|
|
226
|
+
if (minuteStep !== null) time = `Every ${String(minuteStep)} minutes of ${hours}`;
|
|
227
|
+
else time = `At minute ${listOf(schedule.minutes.map(String), isConsecutive(schedule.minutes))} past ${hours}`;
|
|
228
|
+
} else if (minuteStep !== null) time = `Every ${String(minuteStep)} minutes during ${listOf(schedule.hours.map((h) => formatTime(h, 0, locale)), isConsecutive(schedule.hours))}`;
|
|
229
|
+
else if (schedule.minutes.length === 1) {
|
|
230
|
+
const minute = schedule.minutes[0] ?? 0;
|
|
231
|
+
time = `At ${listOf(schedule.hours.map((h) => formatTime(h, minute, locale)), false)}`;
|
|
232
|
+
} else time = `At minute ${listOf(schedule.minutes.map(String), isConsecutive(schedule.minutes))} past ${listOf(schedule.hours.map((h) => formatTime(h, 0, locale)), isConsecutive(schedule.hours))}`;
|
|
233
|
+
const dayPhrases = [];
|
|
234
|
+
if (schedule.dayOfMonthRestricted) {
|
|
235
|
+
const step = stepOf(dayText);
|
|
236
|
+
dayPhrases.push(step !== null ? `${every("day", step)} of the month` : `on day ${listOf(schedule.daysOfMonth.map(String), isConsecutive(schedule.daysOfMonth))} of the month`);
|
|
237
|
+
}
|
|
238
|
+
if (schedule.dayOfWeekRestricted) dayPhrases.push(`on ${listOf(schedule.daysOfWeek.map((d) => dayName(d, locale)), isConsecutive(schedule.daysOfWeek))}`);
|
|
239
|
+
let days = dayPhrases.join(", or ");
|
|
240
|
+
if (!days && !allMinutes && !allHours && minuteStep === null && hourStep === null) days = "every day";
|
|
241
|
+
if (schedule.months.length < 12) {
|
|
242
|
+
const step = stepOf(monthText);
|
|
243
|
+
const months = step !== null ? every("month", step) : listOf(schedule.months.map((m) => monthName(m, locale)), isConsecutive(schedule.months));
|
|
244
|
+
days = `${days ? `${days} ` : ""}in ${months}`;
|
|
245
|
+
}
|
|
246
|
+
return days ? `${time} ${days}` : time;
|
|
247
|
+
}
|
|
248
|
+
const formatters = /* @__PURE__ */ new Map();
|
|
249
|
+
function formatterFor(timeZone) {
|
|
250
|
+
const key = timeZone ?? "";
|
|
251
|
+
let formatter = formatters.get(key);
|
|
252
|
+
if (!formatter) {
|
|
253
|
+
formatter = new Intl.DateTimeFormat("en-US", {
|
|
254
|
+
timeZone,
|
|
255
|
+
hourCycle: "h23",
|
|
256
|
+
year: "numeric",
|
|
257
|
+
month: "numeric",
|
|
258
|
+
day: "numeric",
|
|
259
|
+
hour: "numeric",
|
|
260
|
+
minute: "numeric",
|
|
261
|
+
weekday: "short"
|
|
262
|
+
});
|
|
263
|
+
formatters.set(key, formatter);
|
|
264
|
+
}
|
|
265
|
+
return formatter;
|
|
266
|
+
}
|
|
267
|
+
function wallClockOf(instant, timeZone) {
|
|
268
|
+
const parts = {};
|
|
269
|
+
for (const part of formatterFor(timeZone).formatToParts(new Date(instant))) parts[part.type] = part.value;
|
|
270
|
+
return {
|
|
271
|
+
year: Number(parts.year),
|
|
272
|
+
month: Number(parts.month),
|
|
273
|
+
day: Number(parts.day),
|
|
274
|
+
hour: Number(parts.hour) % 24,
|
|
275
|
+
minute: Number(parts.minute),
|
|
276
|
+
weekday: DAY_NAMES.indexOf((parts.weekday ?? "").slice(0, 3).toUpperCase())
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
function offsetAt(instant, timeZone) {
|
|
280
|
+
const wall = wallClockOf(instant, timeZone);
|
|
281
|
+
return Date.UTC(wall.year, wall.month - 1, wall.day, wall.hour, wall.minute) - Math.floor(instant / 6e4) * 6e4;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* The instant a wall-clock time names in a zone, or null when it names none —
|
|
285
|
+
* the hour the clocks skip in spring. An ambiguous time, in the hour that
|
|
286
|
+
* repeats in autumn, resolves to its first occurrence.
|
|
287
|
+
*/
|
|
288
|
+
function zonedTimeToInstant(year, month, day, hour, minute, timeZone) {
|
|
289
|
+
const guess = Date.UTC(year, month - 1, day, hour, minute);
|
|
290
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
291
|
+
const oneDay = 864e5;
|
|
292
|
+
for (const seed of [
|
|
293
|
+
guess,
|
|
294
|
+
guess - oneDay,
|
|
295
|
+
guess + oneDay
|
|
296
|
+
]) {
|
|
297
|
+
let instant = seed;
|
|
298
|
+
for (let pass = 0; pass < 2; pass += 1) {
|
|
299
|
+
instant = guess - offsetAt(instant, timeZone);
|
|
300
|
+
candidates.add(instant);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
for (const candidate of [...candidates].sort((a, b) => a - b)) {
|
|
304
|
+
const wall = wallClockOf(candidate, timeZone);
|
|
305
|
+
if (wall.year === year && wall.month === month && wall.day === day && wall.hour === hour && wall.minute === minute) return candidate;
|
|
306
|
+
}
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
/** Roughly five years of days: enough to find the 29th of February, and to give up on the 30th. */
|
|
310
|
+
const MAX_DAYS = 1830;
|
|
311
|
+
/**
|
|
312
|
+
* The next `count` instants the schedule fires, in order.
|
|
313
|
+
*
|
|
314
|
+
* Fewer than `count` are returned when the schedule cannot produce them —
|
|
315
|
+
* `0 0 30 2 *` never runs, and saying so beats a spinner.
|
|
316
|
+
*/
|
|
317
|
+
function nextRuns(expression, options) {
|
|
318
|
+
const { from, count = 5, timeZone } = options;
|
|
319
|
+
const schedule = parseCron(expression);
|
|
320
|
+
const runs = [];
|
|
321
|
+
const start = Math.floor(from.getTime() / 6e4) * 6e4 + 6e4;
|
|
322
|
+
const months = new Set(schedule.months);
|
|
323
|
+
const daysOfMonth = new Set(schedule.daysOfMonth);
|
|
324
|
+
const daysOfWeek = new Set(schedule.daysOfWeek);
|
|
325
|
+
const dayMatches = (wall) => {
|
|
326
|
+
if (!months.has(wall.month)) return false;
|
|
327
|
+
const domMatch = daysOfMonth.has(wall.day);
|
|
328
|
+
const dowMatch = daysOfWeek.has(wall.weekday);
|
|
329
|
+
if (schedule.dayOfMonthRestricted && schedule.dayOfWeekRestricted) return domMatch || dowMatch;
|
|
330
|
+
if (schedule.dayOfMonthRestricted) return domMatch;
|
|
331
|
+
if (schedule.dayOfWeekRestricted) return dowMatch;
|
|
332
|
+
return true;
|
|
333
|
+
};
|
|
334
|
+
const first = wallClockOf(start, timeZone);
|
|
335
|
+
let noon = zonedTimeToInstant(first.year, first.month, first.day, 12, 0, timeZone);
|
|
336
|
+
if (noon === null) return runs;
|
|
337
|
+
for (let i = 0; i < MAX_DAYS && runs.length < count; i += 1, noon += 864e5) {
|
|
338
|
+
const wall = wallClockOf(noon, timeZone);
|
|
339
|
+
if (!dayMatches(wall)) continue;
|
|
340
|
+
for (const hour of schedule.hours) for (const minute of schedule.minutes) {
|
|
341
|
+
const instant = zonedTimeToInstant(wall.year, wall.month, wall.day, hour, minute, timeZone);
|
|
342
|
+
if (instant === null || instant < start) continue;
|
|
343
|
+
runs.push(new Date(instant));
|
|
344
|
+
if (runs.length >= count) return runs;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return runs;
|
|
348
|
+
}
|
|
349
|
+
//#endregion
|
|
350
|
+
export { CRON_SHORTCUTS, CronExpressionError, describeCron, isValidCron, nextRuns, parseCron, zonedTimeToInstant };
|
|
351
|
+
|
|
352
|
+
//# sourceMappingURL=cron-expression.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cron-expression.js","names":[],"sources":["../../../src/components/cron-editor/cron-expression.ts"],"sourcesContent":["/**\n * The five-field cron expression, as a value: parsed, described in words, and\n * projected onto a calendar in a named time zone.\n *\n * The dialect is the POSIX one — minute, hour, day of month, month, day of\n * week — which is what crontab, GitHub Actions, Vercel, Kubernetes and Airflow\n * all read. Not the Quartz one: no seconds field and no `L`, `W` or `#`,\n * because the products people schedule from a form do not accept them, and an\n * expression this editor produces has to run where it is pasted. The `@daily`\n * family of shortcuts is accepted, since crontab accepts it.\n *\n * Everything here is pure and takes its clock as an argument, so the\n * description, the run times and the two things every reimplementation gets\n * wrong — day-of-month OR day-of-week, and a wall-clock time that does not\n * exist on the day the clocks go forward — are tested without rendering\n * anything, and an application can compute the next run on the server from\n * the same expression the editor produced.\n */\n\nexport type CronField = \"minute\" | \"hour\" | \"dayOfMonth\" | \"month\" | \"dayOfWeek\";\n\nexport class CronExpressionError extends Error {\n constructor(\n message: string,\n /** Which of the five fields is wrong, when one is. */\n public readonly field?: CronField,\n ) {\n super(message);\n this.name = \"CronExpressionError\";\n }\n}\n\nexport interface CronSchedule {\n minutes: number[];\n hours: number[];\n daysOfMonth: number[];\n months: number[];\n /** 0 to 6, Sunday first. `7` in the source is folded into 0. */\n daysOfWeek: number[];\n /**\n * Whether each day field was written as something other than `*`. Cron's\n * rule, which every reimplementation gets wrong at least once: when both are\n * restricted a day matches if EITHER does, not both.\n */\n dayOfMonthRestricted: boolean;\n dayOfWeekRestricted: boolean;\n /** The expression with any shortcut expanded. */\n expression: string;\n}\n\nconst FIELDS: readonly CronField[] = [\"minute\", \"hour\", \"dayOfMonth\", \"month\", \"dayOfWeek\"];\n\nconst RANGES: Record<CronField, { min: number; max: number; label: string }> = {\n minute: { min: 0, max: 59, label: \"Minute\" },\n hour: { min: 0, max: 23, label: \"Hour\" },\n dayOfMonth: { min: 1, max: 31, label: \"Day of month\" },\n month: { min: 1, max: 12, label: \"Month\" },\n dayOfWeek: { min: 0, max: 7, label: \"Day of week\" },\n};\n\nconst MONTH_NAMES = [\n \"JAN\",\n \"FEB\",\n \"MAR\",\n \"APR\",\n \"MAY\",\n \"JUN\",\n \"JUL\",\n \"AUG\",\n \"SEP\",\n \"OCT\",\n \"NOV\",\n \"DEC\",\n];\nconst DAY_NAMES = [\"SUN\", \"MON\", \"TUE\", \"WED\", \"THU\", \"FRI\", \"SAT\"];\n\nexport const CRON_SHORTCUTS: Record<string, string> = {\n \"@yearly\": \"0 0 1 1 *\",\n \"@annually\": \"0 0 1 1 *\",\n \"@monthly\": \"0 0 1 * *\",\n \"@weekly\": \"0 0 * * 0\",\n \"@daily\": \"0 0 * * *\",\n \"@midnight\": \"0 0 * * *\",\n \"@hourly\": \"0 * * * *\",\n};\n\nfunction nameToNumber(token: string, field: CronField): number | null {\n const upper = token.toUpperCase();\n if (field === \"month\") {\n const index = MONTH_NAMES.indexOf(upper);\n return index === -1 ? null : index + 1;\n }\n if (field === \"dayOfWeek\") {\n const index = DAY_NAMES.indexOf(upper);\n return index === -1 ? null : index;\n }\n return null;\n}\n\nfunction parseNumber(token: string, field: CronField): number {\n const { min, max, label } = RANGES[field];\n const named = nameToNumber(token, field);\n const value = named ?? (/^\\d+$/.test(token) ? Number(token) : Number.NaN);\n if (Number.isNaN(value)) {\n throw new CronExpressionError(\n `${label}: \"${token}\" is not a number${field === \"month\" || field === \"dayOfWeek\" ? \" or a name\" : \"\"}.`,\n field,\n );\n }\n if (value < min || value > max) {\n throw new CronExpressionError(\n `${label} must be ${String(min)}–${String(max)}, not ${String(value)}.`,\n field,\n );\n }\n return value;\n}\n\n/** One field's set of values, in order and without duplicates. */\nfunction parseField(text: string, field: CronField): { values: number[]; restricted: boolean } {\n const { min, max, label } = RANGES[field];\n // Day of week runs 0–7 in the source and 0–6 in the result.\n const top = field === \"dayOfWeek\" ? 6 : max;\n const values = new Set<number>();\n let restricted = false;\n\n for (const part of text.split(\",\")) {\n if (part === \"\") throw new CronExpressionError(`${label}: empty item in \"${text}\".`, field);\n\n const [rangeText, stepText, ...extra] = part.split(\"/\");\n if (extra.length > 0 || stepText === \"\") {\n throw new CronExpressionError(`${label}: \"${part}\" has a malformed step.`, field);\n }\n const step = stepText === undefined ? 1 : Number(stepText);\n if (stepText !== undefined && (!/^\\d+$/.test(stepText) || step < 1)) {\n throw new CronExpressionError(\n `${label}: step must be a whole number of 1 or more, not \"${stepText}\".`,\n field,\n );\n }\n\n let start: number;\n let end: number;\n if (rangeText === \"*\") {\n start = min;\n end = top;\n if (stepText !== undefined) restricted = true;\n } else if (rangeText === undefined) {\n throw new CronExpressionError(`${label}: \"${part}\" is empty.`, field);\n } else {\n restricted = true;\n const [startText, endText, ...more] = rangeText.split(\"-\");\n if (more.length > 0 || startText === undefined || startText === \"\") {\n throw new CronExpressionError(`${label}: \"${rangeText}\" is not a range.`, field);\n }\n start = parseNumber(startText, field);\n if (endText === undefined) {\n // A bare number with a step, `5/10`, means \"from 5 onwards\" in Vixie cron.\n end = stepText === undefined ? start : top;\n } else {\n if (endText === \"\")\n throw new CronExpressionError(`${label}: \"${rangeText}\" is missing its end.`, field);\n end = parseNumber(endText, field);\n if (end < start) {\n throw new CronExpressionError(\n `${label}: ${String(start)}-${String(end)} runs backwards.`,\n field,\n );\n }\n }\n }\n\n for (let value = start; value <= end; value += step) {\n values.add(field === \"dayOfWeek\" && value === 7 ? 0 : value);\n }\n }\n\n return { values: [...values].sort((a, b) => a - b), restricted };\n}\n\nexport function parseCron(expression: string): CronSchedule {\n const trimmed = expression.trim();\n if (trimmed === \"\") throw new CronExpressionError(\"Enter a schedule.\");\n\n const expanded = trimmed.startsWith(\"@\") ? CRON_SHORTCUTS[trimmed.toLowerCase()] : trimmed;\n if (expanded === undefined) {\n throw new CronExpressionError(\n `\"${trimmed}\" is not a shortcut. Try @hourly, @daily, @weekly, @monthly or @yearly.`,\n );\n }\n\n const parts = expanded.split(/\\s+/);\n if (parts.length !== 5) {\n throw new CronExpressionError(\n `Expected 5 fields — minute, hour, day of month, month, day of week — not ${String(parts.length)}.`,\n );\n }\n\n const fields = FIELDS.map((field, index) => parseField(parts[index] ?? \"\", field));\n const at = (index: number) => fields[index] ?? { values: [], restricted: false };\n const [minute, hour, dayOfMonth, month, dayOfWeek] = [at(0), at(1), at(2), at(3), at(4)];\n\n return {\n minutes: minute.values,\n hours: hour.values,\n daysOfMonth: dayOfMonth.values,\n months: month.values,\n daysOfWeek: dayOfWeek.values,\n dayOfMonthRestricted: dayOfMonth.restricted,\n dayOfWeekRestricted: dayOfWeek.restricted,\n expression: parts.join(\" \"),\n };\n}\n\nexport function isValidCron(expression: string): boolean {\n try {\n parseCron(expression);\n return true;\n } catch {\n return false;\n }\n}\n\n/* ------------------------------------------------------------------ */\n/* Describing */\n/* ------------------------------------------------------------------ */\n\nexport interface DescribeOptions {\n locale?: string;\n}\n\n/** \"1, 2 and 3\", or \"1 to 5\" when the values are consecutive. */\nfunction listOf(items: string[], consecutive: boolean): string {\n if (items.length === 1) return items[0] ?? \"\";\n if (consecutive && items.length > 2)\n return `${items[0] ?? \"\"} to ${items[items.length - 1] ?? \"\"}`;\n return `${items.slice(0, -1).join(\", \")} and ${items[items.length - 1] ?? \"\"}`;\n}\n\nfunction isConsecutive(values: number[]): boolean {\n return values.every((value, index) => index === 0 || value === (values[index - 1] ?? 0) + 1);\n}\n\n/** The step a field was written with, when it was written as a star over a number. */\nfunction stepOf(text: string): number | null {\n const match = /^\\*\\/(\\d+)$/.exec(text);\n return match ? Number(match[1]) : null;\n}\n\nfunction ordinal(n: number): string {\n const rem10 = n % 10;\n const rem100 = n % 100;\n const suffix =\n rem10 === 1 && rem100 !== 11\n ? \"st\"\n : rem10 === 2 && rem100 !== 12\n ? \"nd\"\n : rem10 === 3 && rem100 !== 13\n ? \"rd\"\n : \"th\";\n return `${String(n)}${suffix}`;\n}\n\nfunction formatTime(hour: number, minute: number, locale?: string): string {\n // Formatted in UTC from a UTC instant, so the runtime zone cannot shift it.\n return new Intl.DateTimeFormat(locale, {\n hour: \"2-digit\",\n minute: \"2-digit\",\n timeZone: \"UTC\",\n }).format(Date.UTC(2026, 0, 1, hour, minute));\n}\n\nfunction dayName(index: number, locale?: string): string {\n // 4 January 2026 is a Sunday.\n return new Intl.DateTimeFormat(locale, { weekday: \"long\" }).format(\n new Date(2026, 0, 4 + index),\n );\n}\n\nfunction monthName(index: number, locale?: string): string {\n return new Intl.DateTimeFormat(locale, { month: \"long\" }).format(\n new Date(2026, index - 1, 1),\n );\n}\n\n/**\n * The schedule in a sentence.\n *\n * Every product that has this control renders \"Every Monday at 09:00\" beside\n * the expression, because `0 9 * * 1` is not something most people can read.\n * Throws for an invalid expression; the editor turns that into the field's\n * error text.\n */\nexport function describeCron(expression: string, options: DescribeOptions = {}): string {\n const { locale } = options;\n const schedule = parseCron(expression);\n const [minuteText = \"\", hourText = \"\", dayText = \"\", monthText = \"\"] =\n schedule.expression.split(\" \");\n\n const every = (label: string, n: number) =>\n n === 1 ? `every ${label}` : `every ${ordinal(n)} ${label}`;\n\n // Time of day.\n let time: string;\n const minuteStep = stepOf(minuteText);\n const hourStep = stepOf(hourText);\n const allMinutes = minuteText === \"*\";\n const allHours = hourText === \"*\";\n\n if (allMinutes && allHours) {\n time = \"Every minute\";\n } else if (minuteStep !== null && allHours) {\n time = `Every ${minuteStep === 1 ? \"minute\" : `${String(minuteStep)} minutes`}`;\n } else if (allMinutes) {\n time = `Every minute of ${listOf(\n schedule.hours.map((h) => formatTime(h, 0, locale)),\n isConsecutive(schedule.hours),\n )}`;\n } else if (allHours || hourStep !== null) {\n const hours = hourStep !== null && hourStep > 1 ? every(\"hour\", hourStep) : \"every hour\";\n if (minuteStep !== null) {\n time = `Every ${String(minuteStep)} minutes of ${hours}`;\n } else {\n time = `At minute ${listOf(schedule.minutes.map(String), isConsecutive(schedule.minutes))} past ${hours}`;\n }\n } else if (minuteStep !== null) {\n time = `Every ${String(minuteStep)} minutes during ${listOf(\n schedule.hours.map((h) => formatTime(h, 0, locale)),\n isConsecutive(schedule.hours),\n )}`;\n } else if (schedule.minutes.length === 1) {\n const minute = schedule.minutes[0] ?? 0;\n time = `At ${listOf(\n schedule.hours.map((h) => formatTime(h, minute, locale)),\n false,\n )}`;\n } else {\n time = `At minute ${listOf(schedule.minutes.map(String), isConsecutive(schedule.minutes))} past ${listOf(\n schedule.hours.map((h) => formatTime(h, 0, locale)),\n isConsecutive(schedule.hours),\n )}`;\n }\n\n // Days. When both day fields are restricted a day matches if either does —\n // said with \"or\", because \"and\" is what readers assume and it is wrong.\n const dayPhrases: string[] = [];\n if (schedule.dayOfMonthRestricted) {\n const step = stepOf(dayText);\n dayPhrases.push(\n step !== null\n ? `${every(\"day\", step)} of the month`\n : `on day ${listOf(schedule.daysOfMonth.map(String), isConsecutive(schedule.daysOfMonth))} of the month`,\n );\n }\n if (schedule.dayOfWeekRestricted) {\n dayPhrases.push(\n `on ${listOf(\n schedule.daysOfWeek.map((d) => dayName(d, locale)),\n isConsecutive(schedule.daysOfWeek),\n )}`,\n );\n }\n let days = dayPhrases.join(\", or \");\n\n // A fixed time of day with no day named is every day. \"Every 15 minutes\"\n // already says how often, and \"every day\" after it would be noise.\n const fixedTime = !allMinutes && !allHours && minuteStep === null && hourStep === null;\n if (!days && fixedTime) days = \"every day\";\n\n if (schedule.months.length < 12) {\n const step = stepOf(monthText);\n const months =\n step !== null\n ? every(\"month\", step)\n : listOf(\n schedule.months.map((m) => monthName(m, locale)),\n isConsecutive(schedule.months),\n );\n days = `${days ? `${days} ` : \"\"}in ${months}`;\n }\n\n return days ? `${time} ${days}` : time;\n}\n\n/* ------------------------------------------------------------------ */\n/* Next runs */\n/* ------------------------------------------------------------------ */\n\nexport interface NextRunsOptions {\n /** Runs strictly after this instant. */\n from: Date;\n count?: number;\n /** IANA zone the expression's wall-clock times are in. Defaults to the runtime zone. */\n timeZone?: string;\n}\n\ninterface WallClock {\n year: number;\n month: number;\n day: number;\n hour: number;\n minute: number;\n /** 0 = Sunday. */\n weekday: number;\n}\n\nconst formatters = new Map<string, Intl.DateTimeFormat>();\n\nfunction formatterFor(timeZone: string | undefined): Intl.DateTimeFormat {\n const key = timeZone ?? \"\";\n let formatter = formatters.get(key);\n if (!formatter) {\n formatter = new Intl.DateTimeFormat(\"en-US\", {\n timeZone,\n hourCycle: \"h23\",\n year: \"numeric\",\n month: \"numeric\",\n day: \"numeric\",\n hour: \"numeric\",\n minute: \"numeric\",\n weekday: \"short\",\n });\n formatters.set(key, formatter);\n }\n return formatter;\n}\n\nfunction wallClockOf(instant: number, timeZone: string | undefined): WallClock {\n const parts: Record<string, string> = {};\n for (const part of formatterFor(timeZone).formatToParts(new Date(instant))) {\n parts[part.type] = part.value;\n }\n return {\n year: Number(parts.year),\n month: Number(parts.month),\n day: Number(parts.day),\n hour: Number(parts.hour) % 24,\n minute: Number(parts.minute),\n weekday: DAY_NAMES.indexOf((parts.weekday ?? \"\").slice(0, 3).toUpperCase()),\n };\n}\n\nfunction offsetAt(instant: number, timeZone: string | undefined): number {\n const wall = wallClockOf(instant, timeZone);\n return (\n Date.UTC(wall.year, wall.month - 1, wall.day, wall.hour, wall.minute) -\n Math.floor(instant / 60_000) * 60_000\n );\n}\n\n/**\n * The instant a wall-clock time names in a zone, or null when it names none —\n * the hour the clocks skip in spring. An ambiguous time, in the hour that\n * repeats in autumn, resolves to its first occurrence.\n */\nexport function zonedTimeToInstant(\n year: number,\n month: number,\n day: number,\n hour: number,\n minute: number,\n timeZone: string | undefined,\n): number | null {\n const guess = Date.UTC(year, month - 1, day, hour, minute);\n // Each pass corrects the guess by the offset in force at the previous\n // answer; across a DST change the two answers are the two candidates.\n const candidates = new Set<number>();\n // Seeded from a day either side as well, so the offset that stopped\n // applying an hour ago — the one that makes an autumn time ambiguous — is\n // still tried.\n const oneDay = 24 * 60 * 60_000;\n for (const seed of [guess, guess - oneDay, guess + oneDay]) {\n let instant = seed;\n for (let pass = 0; pass < 2; pass += 1) {\n instant = guess - offsetAt(instant, timeZone);\n candidates.add(instant);\n }\n }\n for (const candidate of [...candidates].sort((a, b) => a - b)) {\n const wall = wallClockOf(candidate, timeZone);\n if (\n wall.year === year &&\n wall.month === month &&\n wall.day === day &&\n wall.hour === hour &&\n wall.minute === minute\n ) {\n return candidate;\n }\n }\n return null;\n}\n\n/** Roughly five years of days: enough to find the 29th of February, and to give up on the 30th. */\nconst MAX_DAYS = 366 * 5;\n\n/**\n * The next `count` instants the schedule fires, in order.\n *\n * Fewer than `count` are returned when the schedule cannot produce them —\n * `0 0 30 2 *` never runs, and saying so beats a spinner.\n */\nexport function nextRuns(expression: string, options: NextRunsOptions): Date[] {\n const { from, count = 5, timeZone } = options;\n const schedule = parseCron(expression);\n const runs: Date[] = [];\n\n const start = Math.floor(from.getTime() / 60_000) * 60_000 + 60_000;\n const months = new Set(schedule.months);\n const daysOfMonth = new Set(schedule.daysOfMonth);\n const daysOfWeek = new Set(schedule.daysOfWeek);\n\n const dayMatches = (wall: WallClock): boolean => {\n if (!months.has(wall.month)) return false;\n const domMatch = daysOfMonth.has(wall.day);\n const dowMatch = daysOfWeek.has(wall.weekday);\n if (schedule.dayOfMonthRestricted && schedule.dayOfWeekRestricted)\n return domMatch || dowMatch;\n if (schedule.dayOfMonthRestricted) return domMatch;\n if (schedule.dayOfWeekRestricted) return dowMatch;\n return true;\n };\n\n // Walk day by day from noon, which no DST change touches, so that stepping\n // 24 hours always lands in the next calendar day.\n const first = wallClockOf(start, timeZone);\n let noon = zonedTimeToInstant(first.year, first.month, first.day, 12, 0, timeZone);\n if (noon === null) return runs;\n\n for (let i = 0; i < MAX_DAYS && runs.length < count; i += 1, noon += 24 * 60 * 60_000) {\n const wall = wallClockOf(noon, timeZone);\n if (!dayMatches(wall)) continue;\n\n for (const hour of schedule.hours) {\n for (const minute of schedule.minutes) {\n const instant = zonedTimeToInstant(\n wall.year,\n wall.month,\n wall.day,\n hour,\n minute,\n timeZone,\n );\n if (instant === null || instant < start) continue;\n runs.push(new Date(instant));\n if (runs.length >= count) return runs;\n }\n }\n }\n\n return runs;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { Cron, CronBuilder, CronBuilderProps, CronExpression, CronExpressionProps, CronNextRuns, CronNextRunsProps, CronProps } from "./cron-editor.js";
|
|
2
|
+
import { CRON_SHORTCUTS, CronExpressionError, CronField, CronSchedule, DescribeOptions, NextRunsOptions, describeCron, isValidCron, nextRuns, parseCron, zonedTimeToInstant } from "./cron-expression.js";
|
|
3
|
+
export { CRON_SHORTCUTS, Cron, CronBuilder, type CronBuilderProps, CronExpression, CronExpressionError, type CronExpressionProps, type CronField, CronNextRuns, type CronNextRunsProps, type CronProps, type CronSchedule, type DescribeOptions, type NextRunsOptions, describeCron, isValidCron, nextRuns, parseCron, zonedTimeToInstant };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { CRON_SHORTCUTS, CronExpressionError, describeCron, isValidCron, nextRuns, parseCron, zonedTimeToInstant } from "./cron-expression.js";
|
|
2
|
+
import { Cron, CronBuilder, CronExpression, CronNextRuns } from "./cron-editor.js";
|
|
3
|
+
export { CRON_SHORTCUTS, Cron, CronBuilder, CronExpression, CronExpressionError, CronNextRuns, describeCron, isValidCron, nextRuns, parseCron, zonedTimeToInstant };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineMeta } from "../../registry/schema.js";
|
|
2
|
+
//#region src/components/cron-editor/meta.ts
|
|
3
|
+
const meta = defineMeta({
|
|
4
|
+
name: "cron-editor",
|
|
5
|
+
title: "Cron Editor",
|
|
6
|
+
description: "A schedule as a cron expression, with a plain-language reading and the next runs.",
|
|
7
|
+
category: "form",
|
|
8
|
+
status: "stable",
|
|
9
|
+
dependencies: [],
|
|
10
|
+
registryDependencies: ["input", "select"],
|
|
11
|
+
files: ["cron-expression.ts", "cron-editor.tsx"],
|
|
12
|
+
a11y: "The expression field is described by one element that holds either the plain-language reading or the reason the expression is invalid, so a reader hears the error replace the reading rather than both at once; an invalid expression is never applied. The builder's day buttons are a named group of aria-pressed toggles with the full day name as the accessible name, and the last selected day cannot be removed silently — the group says why. Days 29 to 31 say in text that shorter months skip them. Next runs are a list of time elements with machine-readable datetimes, headed by the zone they are in, because a time with no zone is the classic scheduling mistake. Nothing clock-dependent renders until the clock is known, so server and client cannot disagree."
|
|
13
|
+
});
|
|
14
|
+
//#endregion
|
|
15
|
+
export { meta };
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=meta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meta.js","names":[],"sources":["../../../src/components/cron-editor/meta.ts"],"sourcesContent":["import { defineMeta } from \"@/registry/schema\";\n\nexport const meta = defineMeta({\n name: \"cron-editor\",\n title: \"Cron Editor\",\n description:\n \"A schedule as a cron expression, with a plain-language reading and the next runs.\",\n category: \"form\",\n status: \"stable\",\n dependencies: [],\n registryDependencies: [\"input\", \"select\"],\n files: [\"cron-expression.ts\", \"cron-editor.tsx\"],\n a11y:\n \"The expression field is described by one element that holds either the plain-language \" +\n \"reading or the reason the expression is invalid, so a reader hears the error replace the \" +\n \"reading rather than both at once; an invalid expression is never applied. The builder's \" +\n \"day buttons are a named group of aria-pressed toggles with the full day name as the \" +\n \"accessible name, and the last selected day cannot be removed silently — the group says why. \" +\n \"Days 29 to 31 say in text that shorter months skip them. Next runs are a list of time \" +\n \"elements with machine-readable datetimes, headed by the zone they are in, because a time \" +\n \"with no zone is the classic scheduling mistake. Nothing clock-dependent renders until the \" +\n \"clock is known, so server and client cannot disagree.\",\n});\n"],"mappings":";;AAEA,MAAa,OAAO,WAAW;CAC7B,MAAM;CACN,OAAO;CACP,aACE;CACF,UAAU;CACV,QAAQ;CACR,cAAc,CAAC;CACf,sBAAsB,CAAC,SAAS,QAAQ;CACxC,OAAO,CAAC,sBAAsB,iBAAiB;CAC/C,MACE;AASJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-table.d.ts","names":[],"sources":["../../../src/components/data-table/data-table.tsx"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"data-table.d.ts","names":[],"sources":["../../../src/components/data-table/data-table.tsx"],"mappings":";;;UAsIiB,eACf,kBAAkB,eAClB,cAAc,iBACN,KAAK;EACb,OAAO,MAAc,WAAW;;EAEhC,QAAQ;;iBAGM,UAAU,kBAAkB,eAAe,cAAc,WACvE,OACA,OACA,cACG,SACF,eAAe,WAAW,yBAAM,IAAA;;;;;;;UAmElB;EACf;EACA;EACA,gBAAgB;;UAGD,mCAAmC;EAClD,QAAQ;EACR;;;;;;;;;iBAUc,wBACd,QACA,OACA,cACG,SACF,6CAA0B,IAAA;;UA6EZ;EACf;IACE;IACA;IACA;IACA,mBAAmB;;;UAIN;EACf,OAAO;EACP;;;iBAIc,uBAAuB,OAAO,SAAqB,4CAAyB,IAAA;;;;;;;;UAwC3E;EACf;IAAS;MAAc;MAAmB;;;EAC1C;EACA,cAAc;EACd;EACA;EACA;EACA;;UAGe;EACf,OAAO;EACP;;;;;;;;EAQA,SAAS;;;;;;;;;iBAUK,sBACd,OACA,WACA,UACC,2CAAwB,IAAA"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { mirrorForDirection } from "../../lib/styles.js";
|
|
2
3
|
import { cn } from "../../lib/utils.js";
|
|
3
4
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../select/select.js";
|
|
4
5
|
import { Button } from "../button/button.js";
|
|
@@ -94,7 +95,7 @@ function DataTableColumnHeader({ column, title, className, ...props }) {
|
|
|
94
95
|
children: /* @__PURE__ */ jsxs(Button, {
|
|
95
96
|
variant: "ghost",
|
|
96
97
|
size: "sm",
|
|
97
|
-
className: "-
|
|
98
|
+
className: "-ms-2 h-7 gap-1 px-2 font-medium text-muted-foreground data-[state=open]:bg-accent",
|
|
98
99
|
children: [
|
|
99
100
|
title,
|
|
100
101
|
/* @__PURE__ */ jsx(SortIcon, { direction: sorted === false ? void 0 : sorted }),
|
|
@@ -224,6 +225,7 @@ function DataTablePagination({ table, pageSizes = [
|
|
|
224
225
|
viewBox: "0 0 24 24",
|
|
225
226
|
fill: "none",
|
|
226
227
|
"aria-hidden": "true",
|
|
228
|
+
className: mirrorForDirection,
|
|
227
229
|
children: /* @__PURE__ */ jsx("path", {
|
|
228
230
|
d: "m15 18-6-6 6-6",
|
|
229
231
|
stroke: "currentColor",
|
|
@@ -244,6 +246,7 @@ function DataTablePagination({ table, pageSizes = [
|
|
|
244
246
|
viewBox: "0 0 24 24",
|
|
245
247
|
fill: "none",
|
|
246
248
|
"aria-hidden": "true",
|
|
249
|
+
className: mirrorForDirection,
|
|
247
250
|
children: /* @__PURE__ */ jsx("path", {
|
|
248
251
|
d: "m9 18 6-6-6-6",
|
|
249
252
|
stroke: "currentColor",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-table.js","names":["Table"],"sources":["../../../src/components/data-table/data-table.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n flexRender,\n type RowData,\n type Table as TanstackTable,\n type TableFeatures,\n} from \"@tanstack/react-table\";\nimport type { ComponentPropsWithRef, ReactNode } from \"react\";\n\nimport { Button } from \"@/components/button\";\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"@/components/dropdown-menu\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/select\";\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from \"@/components/table\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Presentation for a TanStack Table instance.\n *\n * Deliberately not a component that takes `data` and `columns` and does\n * everything. Sorting, filtering, selection and pagination are the table\n * library's job and it does them well; wrapping them in props would re-expose\n * an API that already exists, badly, and block anything the wrapper did not\n * anticipate. The consumer owns the instance:\n *\n * const table = useTable({ features, columns, data });\n * <DataTable table={table} />\n *\n * ## How these are typed\n *\n * The table library's features are opt-in, and its types follow: a method like\n * `getCanSort` exists only when the sorting feature is enabled.\n *\n * The controls below therefore declare **the shape they need** rather than\n * being generic over feature flags. A column from a table without sorting does\n * not satisfy `SortableColumn`, so passing one is a compile error at the call\n * site — the guarantee we wanted, and it survives the library reshuffling its\n * internal feature types.\n *\n * `DataTable` itself has to render any table, so it uses the core API and\n * detects the optional extras with real type guards.\n */\n\n/* -------------------------------------------------------------------------- */\n/* Capability detection */\n/* -------------------------------------------------------------------------- */\n\ninterface WithSortState {\n getIsSorted: () => \"asc\" | \"desc\" | false;\n getCanSort: () => boolean;\n}\n\ninterface WithSize {\n getSize: () => number;\n}\n\ninterface WithSelection {\n getIsSelected: () => boolean;\n}\n\ninterface WithVisibility {\n getIsVisible: () => boolean;\n}\n\nfunction hasSortState(column: object): column is WithSortState {\n return \"getIsSorted\" in column && \"getCanSort\" in column;\n}\n\nfunction hasSize(header: object): header is WithSize {\n return \"getSize\" in header;\n}\n\nfunction hasSelection(row: object): row is WithSelection {\n return \"getIsSelected\" in row;\n}\n\nfunction hasVisibility(column: object): column is WithVisibility {\n return \"getIsVisible\" in column;\n}\n\n/**\n * Whether a column's cells should render.\n *\n * A table without the visibility feature has no notion of hidden columns, so\n * everything renders. With the feature on, this has to be honoured for cells as\n * well as headers — dropping a header while still rendering its cells shifts\n * every row out of alignment with the columns above it.\n */\nfunction isColumnVisible(column: object): boolean {\n return !hasVisibility(column) || column.getIsVisible();\n}\n\nfunction ariaSort(column: object): \"ascending\" | \"descending\" | \"none\" | undefined {\n if (!hasSortState(column)) return undefined;\n\n const sorted = column.getIsSorted();\n if (sorted === \"asc\") return \"ascending\";\n if (sorted === \"desc\") return \"descending\";\n return column.getCanSort() ? \"none\" : undefined;\n}\n\nfunction columnWidth(header: object): { width: number } | undefined {\n if (!hasSize(header)) return undefined;\n\n const size = header.getSize();\n return size ? { width: size } : undefined;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Table */\n/* -------------------------------------------------------------------------- */\n\nexport interface DataTableProps<\n TFeatures extends TableFeatures,\n TData extends RowData,\n> extends Omit<ComponentPropsWithRef<\"table\">, \"children\"> {\n table: TanstackTable<TFeatures, TData>;\n /** Shown in place of rows when there are none. */\n empty?: ReactNode;\n}\n\nexport function DataTable<TFeatures extends TableFeatures, TData extends RowData>({\n table,\n empty,\n className,\n ...props\n}: DataTableProps<TFeatures, TData>) {\n const rows = table.getRowModel().rows;\n const columnCount = table.getAllLeafColumns().length;\n\n return (\n <div className=\"rounded-lg border border-border\">\n <Table className={cn(className)} {...props}>\n <TableHeader>\n {table.getHeaderGroups().map((headerGroup) => (\n <TableRow key={headerGroup.id}>\n {headerGroup.headers.map((header) => (\n <TableHead\n key={header.id}\n colSpan={header.colSpan}\n style={columnWidth(header)}\n // Announces the current sort. Without it the arrow icon is the\n // only signal, which is no signal at all for a screen reader.\n aria-sort={ariaSort(header.column)}\n >\n {header.isPlaceholder\n ? null\n : flexRender(header.column.columnDef.header, header.getContext())}\n </TableHead>\n ))}\n </TableRow>\n ))}\n </TableHeader>\n <TableBody>\n {rows.length === 0 ? (\n <TableRow className=\"hover:bg-transparent\">\n <TableCell colSpan={columnCount} className=\"h-32 p-0 text-center\">\n {empty ?? <span className=\"text-sm text-muted-foreground\">No results.</span>}\n </TableCell>\n </TableRow>\n ) : (\n rows.map((row) => (\n <TableRow\n key={row.id}\n data-state={hasSelection(row) && row.getIsSelected() ? \"selected\" : undefined}\n >\n {row\n .getAllCells()\n .filter((cell) => isColumnVisible(cell.column))\n .map((cell) => (\n <TableCell key={cell.id}>\n {flexRender(cell.column.columnDef.cell, cell.getContext())}\n </TableCell>\n ))}\n </TableRow>\n ))\n )}\n </TableBody>\n </Table>\n </div>\n );\n}\n\n/* -------------------------------------------------------------------------- */\n/* Sortable column header */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The slice of a column a sortable header needs.\n *\n * A column from a table without the sorting feature does not have these\n * methods, so passing one is a compile error rather than a runtime crash.\n */\nexport interface SortableColumn {\n getCanSort: () => boolean;\n getIsSorted: () => \"asc\" | \"desc\" | false;\n toggleSorting: (desc?: boolean) => void;\n}\n\nexport interface DataTableColumnHeaderProps extends ComponentPropsWithRef<\"div\"> {\n column: SortableColumn;\n title: string;\n}\n\n/**\n * A sortable column header.\n *\n * Falls back to plain text when the column cannot be sorted, rather than\n * rendering a button that does nothing — a control that looks interactive and\n * is not is worse than no control.\n */\nexport function DataTableColumnHeader({\n column,\n title,\n className,\n ...props\n}: DataTableColumnHeaderProps) {\n if (!column.getCanSort()) {\n return (\n <div className={cn(\"text-xs font-medium\", className)} {...props}>\n {title}\n </div>\n );\n }\n\n const sorted = column.getIsSorted();\n\n return (\n <div className={cn(\"flex items-center\", className)} {...props}>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"-ml-2 h-7 gap-1 px-2 font-medium text-muted-foreground data-[state=open]:bg-accent\"\n >\n {title}\n <SortIcon direction={sorted === false ? undefined : sorted} />\n <span className=\"sr-only\">\n {sorted === \"asc\"\n ? `${title}, sorted ascending`\n : sorted === \"desc\"\n ? `${title}, sorted descending`\n : `${title}, not sorted`}\n </span>\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n <DropdownMenuItem\n onSelect={() => {\n column.toggleSorting(false);\n }}\n >\n Sort ascending\n </DropdownMenuItem>\n <DropdownMenuItem\n onSelect={() => {\n column.toggleSorting(true);\n }}\n >\n Sort descending\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n );\n}\n\nfunction SortIcon({ direction }: { direction?: \"asc\" | \"desc\" }) {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\" className=\"size-3.5 opacity-70\">\n <path\n d={\n direction === \"asc\"\n ? \"m7 14 5-5 5 5\"\n : direction === \"desc\"\n ? \"m7 10 5 5 5-5\"\n : \"m8 10 4-4 4 4M8 14l4 4 4-4\"\n }\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n}\n\n/* -------------------------------------------------------------------------- */\n/* Column visibility */\n/* -------------------------------------------------------------------------- */\n\n/** The slice of a table the column toggle needs. */\nexport interface HideableTable {\n getAllColumns: () => {\n id: string;\n getCanHide: () => boolean;\n getIsVisible: () => boolean;\n toggleVisibility: (visible?: boolean) => void;\n }[];\n}\n\nexport interface DataTableViewOptionsProps {\n table: HideableTable;\n label?: string;\n}\n\n/** Toggles column visibility. */\nexport function DataTableViewOptions({ table, label = \"Columns\" }: DataTableViewOptionsProps) {\n const columns = table.getAllColumns().filter((column) => column.getCanHide());\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" size=\"sm\">\n {label}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-44\">\n <DropdownMenuLabel>Toggle columns</DropdownMenuLabel>\n <DropdownMenuSeparator />\n {columns.map((column) => (\n <DropdownMenuCheckboxItem\n key={column.id}\n checked={column.getIsVisible()}\n onCheckedChange={(checked) => {\n column.toggleVisibility(checked === true);\n }}\n >\n {column.id}\n </DropdownMenuCheckboxItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\n/* -------------------------------------------------------------------------- */\n/* Pagination */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The slice of a table the page controls need.\n *\n * `state` is a property, not a `getState()` call — the table library moved to a\n * reactive state object, and reading it the old way fails at runtime rather\n * than at compile time.\n */\nexport interface PaginatedTable {\n state: { pagination: { pageIndex: number; pageSize: number } };\n getPageCount: () => number;\n setPageSize: (size: number) => void;\n previousPage: () => void;\n nextPage: () => void;\n getCanPreviousPage: () => boolean;\n getCanNextPage: () => boolean;\n}\n\nexport interface DataTablePaginationProps {\n table: PaginatedTable;\n pageSizes?: number[];\n /**\n * Replaces the page position on the left.\n *\n * A slot rather than a `showSelectionCount` flag: a selection count needs the\n * selection and filtering features, and demanding those here would break\n * every table that only paginates.\n */\n status?: ReactNode;\n}\n\n/**\n * Page controls for a table.\n *\n * The page position is a polite live region: paging replaces the rows in place,\n * and without an announcement a screen reader user gets no confirmation that\n * anything happened.\n */\nexport function DataTablePagination({\n table,\n pageSizes = [10, 20, 50, 100],\n status,\n}: DataTablePaginationProps) {\n const { pageIndex, pageSize } = table.state.pagination;\n const pageCount = table.getPageCount();\n\n return (\n <div className=\"flex flex-wrap items-center justify-between gap-4 px-1 py-3\">\n <div className=\"text-sm text-muted-foreground\" aria-live=\"polite\">\n {status ?? (\n <>\n Page {pageIndex + 1} of {Math.max(1, pageCount)}\n </>\n )}\n </div>\n\n <div className=\"flex items-center gap-4\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm text-muted-foreground\" id=\"rows-per-page\">\n Rows per page\n </span>\n <Select\n value={String(pageSize)}\n onValueChange={(value) => {\n table.setPageSize(Number(value));\n }}\n >\n <SelectTrigger triggerSize=\"sm\" className=\"w-18\" aria-labelledby=\"rows-per-page\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {pageSizes.map((size) => (\n <SelectItem key={size} value={String(size)}>\n {size}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"flex items-center gap-1\">\n <Button\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label=\"Go to previous page\"\n disabled={!table.getCanPreviousPage()}\n onClick={() => {\n table.previousPage();\n }}\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\">\n <path\n d=\"m15 18-6-6 6-6\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </Button>\n <Button\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label=\"Go to next page\"\n disabled={!table.getCanNextPage()}\n onClick={() => {\n table.nextPage();\n }}\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\">\n <path\n d=\"m9 18 6-6-6-6\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </Button>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"data-table.js","names":["Table"],"sources":["../../../src/components/data-table/data-table.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n flexRender,\n type RowData,\n type Table as TanstackTable,\n type TableFeatures,\n} from \"@tanstack/react-table\";\nimport type { ComponentPropsWithRef, ReactNode } from \"react\";\n\nimport { Button } from \"@/components/button\";\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"@/components/dropdown-menu\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/select\";\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from \"@/components/table\";\nimport { mirrorForDirection } from \"@/lib/styles\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Presentation for a TanStack Table instance.\n *\n * Deliberately not a component that takes `data` and `columns` and does\n * everything. Sorting, filtering, selection and pagination are the table\n * library's job and it does them well; wrapping them in props would re-expose\n * an API that already exists, badly, and block anything the wrapper did not\n * anticipate. The consumer owns the instance:\n *\n * const table = useTable({ features, columns, data });\n * <DataTable table={table} />\n *\n * ## How these are typed\n *\n * The table library's features are opt-in, and its types follow: a method like\n * `getCanSort` exists only when the sorting feature is enabled.\n *\n * The controls below therefore declare **the shape they need** rather than\n * being generic over feature flags. A column from a table without sorting does\n * not satisfy `SortableColumn`, so passing one is a compile error at the call\n * site — the guarantee we wanted, and it survives the library reshuffling its\n * internal feature types.\n *\n * `DataTable` itself has to render any table, so it uses the core API and\n * detects the optional extras with real type guards.\n */\n\n/* -------------------------------------------------------------------------- */\n/* Capability detection */\n/* -------------------------------------------------------------------------- */\n\ninterface WithSortState {\n getIsSorted: () => \"asc\" | \"desc\" | false;\n getCanSort: () => boolean;\n}\n\ninterface WithSize {\n getSize: () => number;\n}\n\ninterface WithSelection {\n getIsSelected: () => boolean;\n}\n\ninterface WithVisibility {\n getIsVisible: () => boolean;\n}\n\nfunction hasSortState(column: object): column is WithSortState {\n return \"getIsSorted\" in column && \"getCanSort\" in column;\n}\n\nfunction hasSize(header: object): header is WithSize {\n return \"getSize\" in header;\n}\n\nfunction hasSelection(row: object): row is WithSelection {\n return \"getIsSelected\" in row;\n}\n\nfunction hasVisibility(column: object): column is WithVisibility {\n return \"getIsVisible\" in column;\n}\n\n/**\n * Whether a column's cells should render.\n *\n * A table without the visibility feature has no notion of hidden columns, so\n * everything renders. With the feature on, this has to be honoured for cells as\n * well as headers — dropping a header while still rendering its cells shifts\n * every row out of alignment with the columns above it.\n */\nfunction isColumnVisible(column: object): boolean {\n return !hasVisibility(column) || column.getIsVisible();\n}\n\nfunction ariaSort(column: object): \"ascending\" | \"descending\" | \"none\" | undefined {\n if (!hasSortState(column)) return undefined;\n\n const sorted = column.getIsSorted();\n if (sorted === \"asc\") return \"ascending\";\n if (sorted === \"desc\") return \"descending\";\n return column.getCanSort() ? \"none\" : undefined;\n}\n\nfunction columnWidth(header: object): { width: number } | undefined {\n if (!hasSize(header)) return undefined;\n\n const size = header.getSize();\n return size ? { width: size } : undefined;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Table */\n/* -------------------------------------------------------------------------- */\n\nexport interface DataTableProps<\n TFeatures extends TableFeatures,\n TData extends RowData,\n> extends Omit<ComponentPropsWithRef<\"table\">, \"children\"> {\n table: TanstackTable<TFeatures, TData>;\n /** Shown in place of rows when there are none. */\n empty?: ReactNode;\n}\n\nexport function DataTable<TFeatures extends TableFeatures, TData extends RowData>({\n table,\n empty,\n className,\n ...props\n}: DataTableProps<TFeatures, TData>) {\n const rows = table.getRowModel().rows;\n const columnCount = table.getAllLeafColumns().length;\n\n return (\n <div className=\"rounded-lg border border-border\">\n <Table className={cn(className)} {...props}>\n <TableHeader>\n {table.getHeaderGroups().map((headerGroup) => (\n <TableRow key={headerGroup.id}>\n {headerGroup.headers.map((header) => (\n <TableHead\n key={header.id}\n colSpan={header.colSpan}\n style={columnWidth(header)}\n // Announces the current sort. Without it the arrow icon is the\n // only signal, which is no signal at all for a screen reader.\n aria-sort={ariaSort(header.column)}\n >\n {header.isPlaceholder\n ? null\n : flexRender(header.column.columnDef.header, header.getContext())}\n </TableHead>\n ))}\n </TableRow>\n ))}\n </TableHeader>\n <TableBody>\n {rows.length === 0 ? (\n <TableRow className=\"hover:bg-transparent\">\n <TableCell colSpan={columnCount} className=\"h-32 p-0 text-center\">\n {empty ?? <span className=\"text-sm text-muted-foreground\">No results.</span>}\n </TableCell>\n </TableRow>\n ) : (\n rows.map((row) => (\n <TableRow\n key={row.id}\n data-state={hasSelection(row) && row.getIsSelected() ? \"selected\" : undefined}\n >\n {row\n .getAllCells()\n .filter((cell) => isColumnVisible(cell.column))\n .map((cell) => (\n <TableCell key={cell.id}>\n {flexRender(cell.column.columnDef.cell, cell.getContext())}\n </TableCell>\n ))}\n </TableRow>\n ))\n )}\n </TableBody>\n </Table>\n </div>\n );\n}\n\n/* -------------------------------------------------------------------------- */\n/* Sortable column header */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The slice of a column a sortable header needs.\n *\n * A column from a table without the sorting feature does not have these\n * methods, so passing one is a compile error rather than a runtime crash.\n */\nexport interface SortableColumn {\n getCanSort: () => boolean;\n getIsSorted: () => \"asc\" | \"desc\" | false;\n toggleSorting: (desc?: boolean) => void;\n}\n\nexport interface DataTableColumnHeaderProps extends ComponentPropsWithRef<\"div\"> {\n column: SortableColumn;\n title: string;\n}\n\n/**\n * A sortable column header.\n *\n * Falls back to plain text when the column cannot be sorted, rather than\n * rendering a button that does nothing — a control that looks interactive and\n * is not is worse than no control.\n */\nexport function DataTableColumnHeader({\n column,\n title,\n className,\n ...props\n}: DataTableColumnHeaderProps) {\n if (!column.getCanSort()) {\n return (\n <div className={cn(\"text-xs font-medium\", className)} {...props}>\n {title}\n </div>\n );\n }\n\n const sorted = column.getIsSorted();\n\n return (\n <div className={cn(\"flex items-center\", className)} {...props}>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"-ms-2 h-7 gap-1 px-2 font-medium text-muted-foreground data-[state=open]:bg-accent\"\n >\n {title}\n <SortIcon direction={sorted === false ? undefined : sorted} />\n <span className=\"sr-only\">\n {sorted === \"asc\"\n ? `${title}, sorted ascending`\n : sorted === \"desc\"\n ? `${title}, sorted descending`\n : `${title}, not sorted`}\n </span>\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n <DropdownMenuItem\n onSelect={() => {\n column.toggleSorting(false);\n }}\n >\n Sort ascending\n </DropdownMenuItem>\n <DropdownMenuItem\n onSelect={() => {\n column.toggleSorting(true);\n }}\n >\n Sort descending\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n );\n}\n\nfunction SortIcon({ direction }: { direction?: \"asc\" | \"desc\" }) {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\" className=\"size-3.5 opacity-70\">\n <path\n d={\n direction === \"asc\"\n ? \"m7 14 5-5 5 5\"\n : direction === \"desc\"\n ? \"m7 10 5 5 5-5\"\n : \"m8 10 4-4 4 4M8 14l4 4 4-4\"\n }\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n}\n\n/* -------------------------------------------------------------------------- */\n/* Column visibility */\n/* -------------------------------------------------------------------------- */\n\n/** The slice of a table the column toggle needs. */\nexport interface HideableTable {\n getAllColumns: () => {\n id: string;\n getCanHide: () => boolean;\n getIsVisible: () => boolean;\n toggleVisibility: (visible?: boolean) => void;\n }[];\n}\n\nexport interface DataTableViewOptionsProps {\n table: HideableTable;\n label?: string;\n}\n\n/** Toggles column visibility. */\nexport function DataTableViewOptions({ table, label = \"Columns\" }: DataTableViewOptionsProps) {\n const columns = table.getAllColumns().filter((column) => column.getCanHide());\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" size=\"sm\">\n {label}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-44\">\n <DropdownMenuLabel>Toggle columns</DropdownMenuLabel>\n <DropdownMenuSeparator />\n {columns.map((column) => (\n <DropdownMenuCheckboxItem\n key={column.id}\n checked={column.getIsVisible()}\n onCheckedChange={(checked) => {\n column.toggleVisibility(checked === true);\n }}\n >\n {column.id}\n </DropdownMenuCheckboxItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\n/* -------------------------------------------------------------------------- */\n/* Pagination */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The slice of a table the page controls need.\n *\n * `state` is a property, not a `getState()` call — the table library moved to a\n * reactive state object, and reading it the old way fails at runtime rather\n * than at compile time.\n */\nexport interface PaginatedTable {\n state: { pagination: { pageIndex: number; pageSize: number } };\n getPageCount: () => number;\n setPageSize: (size: number) => void;\n previousPage: () => void;\n nextPage: () => void;\n getCanPreviousPage: () => boolean;\n getCanNextPage: () => boolean;\n}\n\nexport interface DataTablePaginationProps {\n table: PaginatedTable;\n pageSizes?: number[];\n /**\n * Replaces the page position on the left.\n *\n * A slot rather than a `showSelectionCount` flag: a selection count needs the\n * selection and filtering features, and demanding those here would break\n * every table that only paginates.\n */\n status?: ReactNode;\n}\n\n/**\n * Page controls for a table.\n *\n * The page position is a polite live region: paging replaces the rows in place,\n * and without an announcement a screen reader user gets no confirmation that\n * anything happened.\n */\nexport function DataTablePagination({\n table,\n pageSizes = [10, 20, 50, 100],\n status,\n}: DataTablePaginationProps) {\n const { pageIndex, pageSize } = table.state.pagination;\n const pageCount = table.getPageCount();\n\n return (\n <div className=\"flex flex-wrap items-center justify-between gap-4 px-1 py-3\">\n <div className=\"text-sm text-muted-foreground\" aria-live=\"polite\">\n {status ?? (\n <>\n Page {pageIndex + 1} of {Math.max(1, pageCount)}\n </>\n )}\n </div>\n\n <div className=\"flex items-center gap-4\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm text-muted-foreground\" id=\"rows-per-page\">\n Rows per page\n </span>\n <Select\n value={String(pageSize)}\n onValueChange={(value) => {\n table.setPageSize(Number(value));\n }}\n >\n <SelectTrigger triggerSize=\"sm\" className=\"w-18\" aria-labelledby=\"rows-per-page\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {pageSizes.map((size) => (\n <SelectItem key={size} value={String(size)}>\n {size}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"flex items-center gap-1\">\n <Button\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label=\"Go to previous page\"\n disabled={!table.getCanPreviousPage()}\n onClick={() => {\n table.previousPage();\n }}\n >\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n aria-hidden=\"true\"\n className={mirrorForDirection}\n >\n <path\n d=\"m15 18-6-6 6-6\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </Button>\n <Button\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label=\"Go to next page\"\n disabled={!table.getCanNextPage()}\n onClick={() => {\n table.nextPage();\n }}\n >\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n aria-hidden=\"true\"\n className={mirrorForDirection}\n >\n <path\n d=\"m9 18 6-6-6-6\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </Button>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -30,7 +30,7 @@ function DialogContent({ className, children, showCloseButton = true, ...props }
|
|
|
30
30
|
children: [children, showCloseButton ? /* @__PURE__ */ jsx(Dialog.Close, {
|
|
31
31
|
"data-slot": "dialog-close",
|
|
32
32
|
"aria-label": "Close",
|
|
33
|
-
className: cn("absolute
|
|
33
|
+
className: cn("absolute end-4 top-4 grid size-7 place-items-center rounded-md text-muted-foreground", "transition-colors duration-[var(--duration-fast)] hover:bg-accent hover:text-foreground", "[&_svg]:size-4", focusRing),
|
|
34
34
|
children: /* @__PURE__ */ jsx("svg", {
|
|
35
35
|
viewBox: "0 0 24 24",
|
|
36
36
|
fill: "none",
|
|
@@ -48,7 +48,7 @@ function DialogContent({ className, children, showCloseButton = true, ...props }
|
|
|
48
48
|
function DialogHeader({ className, ...props }) {
|
|
49
49
|
return /* @__PURE__ */ jsx("div", {
|
|
50
50
|
"data-slot": "dialog-header",
|
|
51
|
-
className: cn("flex flex-col gap-1.5
|
|
51
|
+
className: cn("flex flex-col gap-1.5 pe-8 text-start", className),
|
|
52
52
|
...props
|
|
53
53
|
});
|
|
54
54
|
}
|