diff --git a/src/pretix/base/reldate.py b/src/pretix/base/reldate.py
index 1f609bc1d0..b16cb7907b 100644
--- a/src/pretix/base/reldate.py
+++ b/src/pretix/base/reldate.py
@@ -21,7 +21,7 @@
#
import datetime
from collections import namedtuple
-from typing import Union
+from typing import Tuple, Union
from zoneinfo import ZoneInfo
from dateutil import parser
@@ -42,6 +42,7 @@ EVENT_CHOICES = (
('presale_end', _('Presale end')),
)
+# extend NO_BEFORE_VALUES in reldate.js if changed
ORDER_CHOICES = (
('datetime', _('Moment of order')),
)
diff --git a/src/pretix/base/templates/pretixbase/forms/widgets/reldate.html b/src/pretix/base/templates/pretixbase/forms/widgets/reldate.html
index 73b8ff7c3d..b3113e2584 100644
--- a/src/pretix/base/templates/pretixbase/forms/widgets/reldate.html
+++ b/src/pretix/base/templates/pretixbase/forms/widgets/reldate.html
@@ -1,4 +1,6 @@
{% load i18n %}
+{% load static %}
+
{% for group_name, group_choices, group-index in widget.subwidgets.0.optgroups %}
{% for selopt in group_choices %}
diff --git a/src/pretix/base/templates/pretixbase/forms/widgets/reldatetime.html b/src/pretix/base/templates/pretixbase/forms/widgets/reldatetime.html
index 3e618d0910..31acdf0614 100644
--- a/src/pretix/base/templates/pretixbase/forms/widgets/reldatetime.html
+++ b/src/pretix/base/templates/pretixbase/forms/widgets/reldatetime.html
@@ -1,4 +1,6 @@
{% load i18n %}
+{% load static %}
+
{% for group_name, group_choices, group-index in widget.subwidgets.0.optgroups %}
{% for selopt in group_choices %}
diff --git a/src/pretix/static/pretixbase/js/reldate.js b/src/pretix/static/pretixbase/js/reldate.js
new file mode 100644
index 0000000000..9539a50f86
--- /dev/null
+++ b/src/pretix/static/pretixbase/js/reldate.js
@@ -0,0 +1,44 @@
+if (!window.__reldateInitialized) {
+ window.__reldateInitialized = true;
+ document.addEventListener('DOMContentLoaded', () => {
+ const NO_BEFORE_VALUES = ['datetime'];
+
+ document.querySelectorAll('.reldatetime, .reldate').forEach(container => {
+ const groups = container.querySelectorAll('.radio');
+
+ groups.forEach(group => {
+ const selects = group.querySelectorAll('select');
+ if (selects.length < 2) return;
+
+ let referenceSelect = null;
+ let beforeAfterSelect = null;
+
+ selects.forEach(sel => {
+ const values = Array.from(sel.options).map(o => o.value);
+ // only attach to selects that contain problematic values
+ if (NO_BEFORE_VALUES.some(v => values.includes(v))) {
+ referenceSelect = sel;
+ } else if (values.includes('before') && values.includes('after')) {
+ beforeAfterSelect = sel;
+ }
+ });
+ if (!referenceSelect || !beforeAfterSelect) return;
+
+ const beforeOption = beforeAfterSelect.querySelector('option[value="before"]');
+ const updateBeforeOption = () => {
+ if (NO_BEFORE_VALUES.includes(referenceSelect.value)) {
+ beforeOption.disabled = true;
+ if (beforeAfterSelect.value === 'before') {
+ beforeAfterSelect.value = 'after';
+ beforeAfterSelect.dispatchEvent(new Event('change', { bubbles: true }));
+ }
+ } else {
+ beforeOption.disabled = false;
+ }
+ };
+ referenceSelect.addEventListener('change', updateBeforeOption);
+ updateBeforeOption();
+ });
+ });
+ });
+}