Widget: add versioning support and add v2 with improved a11y-support (#5136)

* Add support for versioning widget.js

* add versionable css

* add version deprecation + redirect

* use dynamic template_path instead of dynamic css_path

* remove dummy code from widget.v1.scss

* fix typo

* [A11y] fix input border & focus style (#5149)

* [A11y] fix input border & focus style

* Fix double semi-colon

* [A11y] make collapse-indicator a button (#5150)

* Fix source order for cart-exists-message (#5152)

* [A11y] underline links (#5151)

* [A11y] Move modal-dialogs to HTMLDialogElement (#5147)

* [A11y] move widget/iframe to html-dialog

* make lightbox a dialog

* move error-alert to dialog

* re-add crossorigin

* fix esc-handling and move animation to icon to enable focusing the button

* fix code-style issues

* block canceling loading iframe

* Escape/cancel blocking fix for Chrome

* add round focus-outline when dialog is loading

* Widget v2: change voucher-link to hash-based link (#5161)

* Fix variants toggle-button being submit-button

* Widget v2: make single-item-select button and always show custom-spinners (#5165)

* Widget v2: make single-item-select=button default

* remove native-spinners and single_item_select

* Stop suggesting old parameter

---------

Co-authored-by: Raphael Michel <michel@rami.io>

* Widget v2: add filter button to events metadata-filter (#5162)

* Widget v2: do not underline events in list and calendar (#5163)

* Fix checkbox button missing border radius (#5158)

* Widget v2: turn add-to-cart-button into resume-button if cart-exists and no items selected (#5160)

* Widget v2: make cart-alert live=polite

* Add resume-button if cart-exists and no items selected

* fix error handling with new-tab and later returning to old window

* Fix cart-message button being full height

* fix amount_selected recalc

* Fix broken v-model

* fix merge

* Widget v2: Remove link from variation-product title (#5159)

* Remove link from variation-product, focus associated input

* open variations onclick on product-title

* clickable elements should be focussable and interactive, so better remove click-handler on product-title

* Widget v2: Fix calendar events color contrast (#5164)

* Widget v2: Fix calendar events color contrast

* fix status-bubbles in list-view

* fix color in mobile

* add striped-background to calendar and week

* improve display of calendar for super small screens

* Fix meta-filter legend not being screen-reader accessible

* update version_default to 2

Co-authored-by: Raphael Michel <michel@rami.io>

---------

Co-authored-by: Raphael Michel <michel@rami.io>
This commit is contained in:
Richard Schreiber
2025-05-28 15:02:39 +02:00
committed by GitHub
parent e46e689f01
commit 92f7456eca
11 changed files with 4130 additions and 500 deletions

View File

@@ -19,8 +19,8 @@
section of your website: section of your website:
{% endblocktrans %} {% endblocktrans %}
</p> </p>
<pre>&lt;link rel="stylesheet" type="text/css" href="{% abseventurl request.event "presale:event.widget.css" %}" crossorigin&gt; <pre>&lt;link rel="stylesheet" type="text/css" href="{% abseventurl request.event "presale:event.widget.css" version=widget_version_default %}" crossorigin&gt;
&lt;script type="text/javascript" src="{{ urlprefix }}{% url "presale:widget.js" lang=form.cleaned_data.language %}" async crossorigin&gt;&lt;/script&gt;</pre> &lt;script type="text/javascript" src="{{ urlprefix }}{% url "presale:widget.js" lang=form.cleaned_data.language version=widget_version_default %}" async crossorigin&gt;&lt;/script&gt;</pre>
<p> <p>
{% blocktrans trimmed %} {% blocktrans trimmed %}
Then, copy the following code to the place of your website where you want the widget to show up: Then, copy the following code to the place of your website where you want the widget to show up:
@@ -32,7 +32,7 @@
{% abseventurl request.event "presale:event.index" as indexurl %} {% abseventurl request.event "presale:event.index" as indexurl %}
{% endif %} {% endif %}
{% if form.cleaned_data.compatibility_mode %} {% if form.cleaned_data.compatibility_mode %}
<pre>&lt;div class="pretix-widget-compat" event="{% abseventurl request.event "presale:event.index" %}"{% if form.cleaned_data.subevent %} subevent="{{ form.cleaned_data.subevent.pk }}"{% endif %}{% if form.cleaned_data.voucher %} voucher="{{ form.cleaned_data.voucher }}"{% endif %} single-item-select="button"&gt;&lt;/div&gt; <pre>&lt;div class="pretix-widget-compat" event="{% abseventurl request.event "presale:event.index" %}"{% if form.cleaned_data.subevent %} subevent="{{ form.cleaned_data.subevent.pk }}"{% endif %}{% if form.cleaned_data.voucher %} voucher="{{ form.cleaned_data.voucher }}"{% endif %}&gt;&lt;/div&gt;
&lt;noscript&gt; &lt;noscript&gt;
&lt;div class="pretix-widget"&gt; &lt;div class="pretix-widget"&gt;
&lt;div class="pretix-widget-info-message"&gt; &lt;div class="pretix-widget-info-message"&gt;
@@ -45,7 +45,7 @@
&lt;/noscript&gt; &lt;/noscript&gt;
</pre> </pre>
{% else %} {% else %}
<pre>&lt;pretix-widget event="{% abseventurl request.event "presale:event.index" %}"{% if form.cleaned_data.subevent %} subevent="{{ form.cleaned_data.subevent.pk }}"{% endif %}{% if form.cleaned_data.voucher %} voucher="{{ form.cleaned_data.voucher }}"{% endif %} single-item-select="button"&gt;&lt;/pretix-widget&gt; <pre>&lt;pretix-widget event="{% abseventurl request.event "presale:event.index" %}"{% if form.cleaned_data.subevent %} subevent="{{ form.cleaned_data.subevent.pk }}"{% endif %}{% if form.cleaned_data.voucher %} voucher="{{ form.cleaned_data.voucher }}"{% endif %}&gt;&lt;/pretix-widget&gt;
&lt;noscript&gt; &lt;noscript&gt;
&lt;div class="pretix-widget"&gt; &lt;div class="pretix-widget"&gt;
&lt;div class="pretix-widget-info-message"&gt; &lt;div class="pretix-widget-info-message"&gt;

View File

@@ -96,6 +96,9 @@ from pretix.control.views.user import RecentAuthenticationRequiredMixin
from pretix.helpers.database import rolledback_transaction from pretix.helpers.database import rolledback_transaction
from pretix.multidomain.urlreverse import build_absolute_uri, get_event_domain from pretix.multidomain.urlreverse import build_absolute_uri, get_event_domain
from pretix.plugins.stripe.payment import StripeSettingsHolder from pretix.plugins.stripe.payment import StripeSettingsHolder
from pretix.presale.views.widget import (
version_default as widget_version_default,
)
from ...base.i18n import language from ...base.i18n import language
from ...base.models.items import ( from ...base.models.items import (
@@ -1408,6 +1411,7 @@ class WidgetSettings(EventSettingsViewMixin, EventPermissionRequiredMixin, FormV
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
ctx['urlprefix'] = settings.SITE_URL ctx['urlprefix'] = settings.SITE_URL
ctx['widget_version_default'] = widget_version_default
domain = get_event_domain(self.request.event, fallback=True) domain = get_event_domain(self.request.event, fallback=True)
if domain: if domain:
siteurlsplit = urlsplit(settings.SITE_URL) siteurlsplit = urlsplit(settings.SITE_URL)

View File

@@ -0,0 +1,5 @@
{% load compress %}
{% load static %}
{% compress css %}
<link rel="stylesheet" type="text/x-scss" href="{% static "pretixpresale/scss/widget.v1.scss" %}"/>
{% endcompress %}

View File

@@ -32,7 +32,7 @@
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License. # License for the specific language governing permissions and limitations under the License.
from django.urls import include, re_path from django.urls import include, path, re_path
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
import pretix.presale.views.cart import pretix.presale.views.cart
@@ -173,7 +173,7 @@ event_patterns = [
re_path(r'^widget/product_list$', pretix.presale.views.widget.WidgetAPIProductList.as_view(), re_path(r'^widget/product_list$', pretix.presale.views.widget.WidgetAPIProductList.as_view(),
name='event.widget.productlist'), name='event.widget.productlist'),
re_path(r'^widget/v1.css$', pretix.presale.views.widget.widget_css, name='event.widget.css'), path('widget/v<int:version>.css', pretix.presale.views.widget.widget_css, name='event.widget.css'),
re_path(r'^(?P<subevent>\d+)/widget/product_list$', pretix.presale.views.widget.WidgetAPIProductList.as_view(), re_path(r'^(?P<subevent>\d+)/widget/product_list$', pretix.presale.views.widget.WidgetAPIProductList.as_view(),
name='event.widget.productlist'), name='event.widget.productlist'),
@@ -196,7 +196,7 @@ organizer_patterns = [
re_path(r'^widget/product_list$', pretix.presale.views.widget.WidgetAPIProductList.as_view(), re_path(r'^widget/product_list$', pretix.presale.views.widget.WidgetAPIProductList.as_view(),
name='organizer.widget.productlist'), name='organizer.widget.productlist'),
re_path(r'^widget/v1.css$', pretix.presale.views.widget.widget_css, name='organizer.widget.css'), path('widget/v<int:version>.css', pretix.presale.views.widget.widget_css, name='organizer.widget.css'),
re_path(r'^theme.css$', pretix.presale.views.theme.theme_css, name='organizer.theme.css'), re_path(r'^theme.css$', pretix.presale.views.theme.theme_css, name='organizer.theme.css'),
re_path(r'^accessibility$', pretix.presale.views.organizer.AccessibilityView.as_view(), name='organizer.accessibility'), re_path(r'^accessibility$', pretix.presale.views.organizer.AccessibilityView.as_view(), name='organizer.accessibility'),
@@ -237,5 +237,5 @@ locale_patterns = [
re_path(r'^robots.txt$', pretix.presale.views.robots.robots_txt, name='robots.txt'), re_path(r'^robots.txt$', pretix.presale.views.robots.robots_txt, name='robots.txt'),
re_path(r'^browserconfig.xml$', pretix.presale.views.theme.browserconfig_xml, name='browserconfig.xml'), re_path(r'^browserconfig.xml$', pretix.presale.views.theme.browserconfig_xml, name='browserconfig.xml'),
re_path(r'^site.webmanifest$', pretix.presale.views.theme.webmanifest, name='site.webmanifest'), re_path(r'^site.webmanifest$', pretix.presale.views.theme.webmanifest, name='site.webmanifest'),
re_path(r'^widget/v1\.(?P<lang>[a-zA-Z0-9_\-]+)\.js$', pretix.presale.views.widget.widget_js, name='widget.js'), path('widget/v<int:version>.<slug:lang>.js', pretix.presale.views.widget.widget_js, name='widget.js'),
] ]

View File

@@ -38,8 +38,10 @@ from django.core.files.base import ContentFile, File
from django.core.files.storage import default_storage from django.core.files.storage import default_storage
from django.db.models import Q from django.db.models import Q
from django.http import FileResponse, Http404, HttpResponse, JsonResponse from django.http import FileResponse, Http404, HttpResponse, JsonResponse
from django.shortcuts import redirect
from django.template import Context, Engine from django.template import Context, Engine
from django.template.loader import get_template from django.template.loader import get_template
from django.urls import reverse
from django.utils.formats import date_format from django.utils.formats import date_format
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import get_language, gettext, pgettext from django.utils.translation import get_language, gettext, pgettext
@@ -81,15 +83,22 @@ logger = logging.getLogger(__name__)
# we never change static source without restart, so we can cache this thread-wise # we never change static source without restart, so we can cache this thread-wise
_source_cache_key = None _source_cache_key = None
version_min = 1
version_max = 2
version_default = 2 # used for output in widget-embed-code
def _get_source_cache_key():
def _get_source_cache_key(version):
global _source_cache_key global _source_cache_key
checksum = hashlib.sha256() checksum = hashlib.sha256()
if not _source_cache_key: if not _source_cache_key:
with open(finders.find("pretixbase/scss/_theme_variables.scss"), "r") as f: with open(finders.find("pretixbase/scss/_theme_variables.scss"), "r") as f:
checksum.update(f.read().encode()) checksum.update(f.read().encode())
tpl = get_template('pretixpresale/widget_dummy.html')
et = html.fromstring(tpl.render({})).xpath('/html/head/link')[0].attrib['href'].replace(settings.STATIC_URL, '') template_path = 'pretixpresale/widget_dummy.html' if version == version_max else 'pretixpresale/widget_dummy.v{}.html'.format(version)
tpl = get_template(template_path)
et = html.fromstring(tpl.render()).xpath('/html/head/link')[0].attrib['href'].replace(settings.STATIC_URL, '')
checksum.update(et.encode()) checksum.update(et.encode())
_source_cache_key = checksum.hexdigest()[:12] _source_cache_key = checksum.hexdigest()[:12]
return _source_cache_key return _source_cache_key
@@ -99,29 +108,39 @@ def indent(s):
return s.replace('\n', '\n ') return s.replace('\n', '\n ')
def widget_css_etag(request, **kwargs): def widget_css_etag(request, version, **kwargs):
# This makes sure a new version of the theme is loaded whenever settings or the source files have changed # This makes sure a new version of the theme is loaded whenever settings or the source files have changed
if hasattr(request, 'event'): if hasattr(request, 'event'):
return (f'{_get_source_cache_key()}-' return (f'{_get_source_cache_key(version)}-'
f'{request.organizer.cache.get_or_set("css_version", default=lambda: int(time.time()))}-' f'{request.organizer.cache.get_or_set("css_version", default=lambda: int(time.time()))}-'
f'{request.event.cache.get_or_set("css_version", default=lambda: int(time.time()))}') f'{request.event.cache.get_or_set("css_version", default=lambda: int(time.time()))}')
else: else:
return f'{_get_source_cache_key()}-{request.organizer.cache.get_or_set("css_version", default=lambda: int(time.time()))}' return f'{_get_source_cache_key(version)}-{request.organizer.cache.get_or_set("css_version", default=lambda: int(time.time()))}'
def widget_js_etag(request, lang, **kwargs): def widget_js_etag(request, version, lang, **kwargs):
gs = GlobalSettingsObject() gs = GlobalSettingsObject()
return gs.settings.get('widget_checksum_{}'.format(lang)) return gs.settings.get('widget_checksum_{}_{}'.format(version, lang))
@gzip_page @gzip_page
@condition(etag_func=widget_css_etag) @condition(etag_func=widget_css_etag)
@cache_page(60) @cache_page(60)
def widget_css(request, **kwargs): def widget_css(request, version, **kwargs):
if version > version_max:
raise Http404()
if version < version_min:
return redirect(reverse('presale:event.widget.css' if hasattr(request, 'event') else 'organizer.widget.css', kwargs={
'version': version_min,
'organizer': request.organizer.slug,
'event': request.event.slug if hasattr(request, 'event') else None,
}))
o = getattr(request, 'event', request.organizer) o = getattr(request, 'event', request.organizer)
tpl = get_template('pretixpresale/widget_dummy.html') template_path = 'pretixpresale/widget_dummy.html' if version == version_max else 'pretixpresale/widget_dummy.v{}.html'.format(version)
et = html.fromstring(tpl.render({})).xpath('/html/head/link')[0].attrib['href'].replace(settings.STATIC_URL, '')
tpl = get_template(template_path)
et = html.fromstring(tpl.render()).xpath('/html/head/link')[0].attrib['href'].replace(settings.STATIC_URL, '')
with open(finders.find(et), 'r') as f: with open(finders.find(et), 'r') as f:
widget_css = f.read() widget_css = f.read()
@@ -134,7 +153,7 @@ def widget_css(request, **kwargs):
return resp return resp
def generate_widget_js(lang): def generate_widget_js(version, lang):
code = [] code = []
with language(lang): with language(lang):
# Provide isolation # Provide isolation
@@ -169,7 +188,7 @@ def generate_widget_js(lang):
'vuejs/vue.js' if settings.DEBUG else 'vuejs/vue.min.js', 'vuejs/vue.js' if settings.DEBUG else 'vuejs/vue.min.js',
'pretixpresale/js/widget/docready.js', 'pretixpresale/js/widget/docready.js',
'pretixpresale/js/widget/floatformat.js', 'pretixpresale/js/widget/floatformat.js',
'pretixpresale/js/widget/widget.js', 'pretixpresale/js/widget/widget.js' if version == version_max else 'pretixpresale/js/widget/widget.v{}.js'.format(version),
] ]
for fname in files: for fname in files:
f = finders.find(fname) f = finders.find(fname)
@@ -188,11 +207,17 @@ def generate_widget_js(lang):
@gzip_page @gzip_page
@condition(etag_func=widget_js_etag) @condition(etag_func=widget_js_etag)
def widget_js(request, lang, **kwargs): def widget_js(request, version, lang, **kwargs):
if lang not in [lc for lc, ll in settings.LANGUAGES]: if version > version_max or lang not in [lc for lc, ll in settings.LANGUAGES]:
raise Http404() raise Http404()
cached_js = cache.get('widget_js_data_{}'.format(lang)) if version < version_min:
return redirect(reverse('presale:widget.js', kwargs={
'version': version_min,
'lang': lang,
}))
cached_js = cache.get('widget_js_data_{}_{}'.format(version, lang))
if cached_js and not settings.DEBUG: if cached_js and not settings.DEBUG:
resp = HttpResponse(cached_js, content_type='text/javascript') resp = HttpResponse(cached_js, content_type='text/javascript')
resp._csp_ignore = True resp._csp_ignore = True
@@ -200,7 +225,7 @@ def widget_js(request, lang, **kwargs):
return resp return resp
gs = GlobalSettingsObject() gs = GlobalSettingsObject()
fname = gs.settings.get('widget_file_{}'.format(lang)) fname = gs.settings.get('widget_file_{}_{}'.format(version, lang))
resp = None resp = None
if fname and not settings.DEBUG: if fname and not settings.DEBUG:
if isinstance(fname, File): if isinstance(fname, File):
@@ -208,21 +233,21 @@ def widget_js(request, lang, **kwargs):
try: try:
data = default_storage.open(fname).read() data = default_storage.open(fname).read()
resp = HttpResponse(data, content_type='text/javascript') resp = HttpResponse(data, content_type='text/javascript')
cache.set('widget_js_data_{}'.format(lang), data, 3600 * 4) cache.set('widget_js_data_{}_{}'.format(version, lang), data, 3600 * 4)
except: except:
logger.exception('Failed to open widget.js') logger.exception('Failed to open widget.js')
if not resp: if not resp:
data = generate_widget_js(lang).encode() data = generate_widget_js(version, lang).encode()
checksum = hashlib.sha1(data).hexdigest() checksum = hashlib.sha1(data).hexdigest()
if not settings.DEBUG: if not settings.DEBUG:
newname = default_storage.save( newname = default_storage.save(
'widget/widget.{}.{}.js'.format(lang, checksum), 'widget/widget.{}.{}.{}.js'.format(version, lang, checksum),
ContentFile(data) ContentFile(data)
) )
gs.settings.set('widget_file_{}'.format(lang), 'file://' + newname) gs.settings.set('widget_file_{}_{}'.format(version, lang), 'file://' + newname)
gs.settings.set('widget_checksum_{}'.format(lang), checksum) gs.settings.set('widget_checksum_{}_{}'.format(version, lang), checksum)
cache.set('widget_js_data_{}'.format(lang), data, 3600 * 4) cache.set('widget_js_data_{}_{}'.format(version, lang), data, 3600 * 4)
resp = HttpResponse(data, content_type='text/javascript') resp = HttpResponse(data, content_type='text/javascript')
resp._csp_ignore = True resp._csp_ignore = True
resp['Access-Control-Allow-Origin'] = '*' resp['Access-Control-Allow-Origin'] = '*'

View File

@@ -14,6 +14,7 @@ $input-color-placeholder: lighten(#000, 70%) !default;
$border-radius-base: var(--pretix-border-radius-base); $border-radius-base: var(--pretix-border-radius-base);
$border-radius-large: var(--pretix-border-radius-large); $border-radius-large: var(--pretix-border-radius-large);
$border-radius-small: var(--pretix-border-radius-small); $border-radius-small: var(--pretix-border-radius-small);
$input-border: #949494 !default;
$navbar-inverse-bg: #3b1c4a !default; $navbar-inverse-bg: #3b1c4a !default;
$navbar-inverse-link-color: white; $navbar-inverse-link-color: white;
@@ -72,7 +73,7 @@ $panel-default-heading-bg: #e5e5e5 !default;
$link-hover-color: var(--pretix-brand-primary-darken-15); $link-hover-color: var(--pretix-brand-primary-darken-15);
$btn-default-border: #CCCCCC; $btn-default-border: #949494;
$btn-primary-border: var(--pretix-brand-primary-darken-5); $btn-primary-border: var(--pretix-brand-primary-darken-5);
$btn-primary-border-active: var(--pretix-brand-primary-darken-30); $btn-primary-border-active: var(--pretix-brand-primary-darken-30);
@@ -122,6 +123,12 @@ $label-warning-bg-hover: var(--pretix-brand-warning-darken-10);
$label-danger-bg: var(--pretix-brand-danger); $label-danger-bg: var(--pretix-brand-danger);
$label-danger-bg-hover: var(--pretix-brand-danger-darken-10); $label-danger-bg-hover: var(--pretix-brand-danger-darken-10);
$alert-primary-bg: var(--pretix-brand-primary-tint-90);
$alert-primary-text: var(--pretix-brand-primary-shade-42);
$alert-primary-border: var(--pretix-brand-primary);
$alert-primary-hr: var(--pretix-brand-primary-darken-5);
$alert-primary-link: var(--pretix-brand-primary-shade-42);
$alert-success-bg: var(--pretix-brand-success-tint-85); $alert-success-bg: var(--pretix-brand-success-tint-85);
$alert-success-text: var(--pretix-brand-success-shade-25); $alert-success-text: var(--pretix-brand-success-shade-25);
$alert-success-border: var(--pretix-brand-success); $alert-success-border: var(--pretix-brand-success);

View File

@@ -16,6 +16,8 @@ var strings = {
'quantity': django.pgettext('widget', 'Quantity'), 'quantity': django.pgettext('widget', 'Quantity'),
'quantity_dec': django.pgettext('widget', 'Decrease quantity'), 'quantity_dec': django.pgettext('widget', 'Decrease quantity'),
'quantity_inc': django.pgettext('widget', 'Increase quantity'), 'quantity_inc': django.pgettext('widget', 'Increase quantity'),
'filter_events_by': django.pgettext('widget', 'Filter events by'),
'filter': django.pgettext('widget', 'Filter'),
'price': django.pgettext('widget', 'Price'), 'price': django.pgettext('widget', 'Price'),
'original_price': django.pgettext('widget', 'Original price: %s'), 'original_price': django.pgettext('widget', 'Original price: %s'),
'new_price': django.pgettext('widget', 'New price: %s'), 'new_price': django.pgettext('widget', 'New price: %s'),
@@ -57,6 +59,8 @@ var strings = {
'redeem': django.pgettext('widget', 'Redeem'), 'redeem': django.pgettext('widget', 'Redeem'),
'voucher_code': django.pgettext('widget', 'Voucher code'), 'voucher_code': django.pgettext('widget', 'Voucher code'),
'close': django.pgettext('widget', 'Close'), 'close': django.pgettext('widget', 'Close'),
'close_checkout': django.pgettext('widget', 'Close checkout'),
'cancel_blocked': django.pgettext('widget', 'You cannot cancel this operation. Please wait for loading to finish.'),
'continue': django.pgettext('widget', 'Continue'), 'continue': django.pgettext('widget', 'Continue'),
'variations': django.pgettext('widget', 'Show variants'), 'variations': django.pgettext('widget', 'Show variants'),
'hide_variations': django.pgettext('widget', 'Hide variants'), 'hide_variations': django.pgettext('widget', 'Hide variants'),
@@ -208,7 +212,7 @@ Vue.component('availbox', {
template: ('<div class="pretix-widget-availability-box">' template: ('<div class="pretix-widget-availability-box">'
+ '<div class="pretix-widget-availability-unavailable"' + '<div class="pretix-widget-availability-unavailable"'
+ ' v-if="item.current_unavailability_reason === \'require_voucher\'">' + ' v-if="item.current_unavailability_reason === \'require_voucher\'">'
+ '<small><a @click.prevent.stop="focus_voucher_field" role="button" tabindex="0">{{unavailability_reason_message}}</a></small>' + '<small><a :href="voucher_jump_link" v-bind:aria-describedby="aria_labelledby">{{unavailability_reason_message}}</a></small>'
+ '</div>' + '</div>'
+ '<div class="pretix-widget-availability-unavailable"' + '<div class="pretix-widget-availability-unavailable"'
+ ' v-else-if="unavailability_reason_message">' + ' v-else-if="unavailability_reason_message">'
@@ -227,24 +231,19 @@ Vue.component('availbox', {
+ '<a :href="waiting_list_url" target="_blank" @click="$root.open_link_in_frame">' + strings.waiting_list + '</a>' + '<a :href="waiting_list_url" target="_blank" @click="$root.open_link_in_frame">' + strings.waiting_list + '</a>'
+ '</div>' + '</div>'
+ '<div class="pretix-widget-availability-available" v-if="!unavailability_reason_message && avail[0] === 100">' + '<div class="pretix-widget-availability-available" v-if="!unavailability_reason_message && avail[0] === 100">'
+ '<label class="pretix-widget-item-count-single-label pretix-widget-btn-checkbox" v-if="order_max === 1 && $root.single_item_select == \'button\'">' + '<label class="pretix-widget-item-count-single-label pretix-widget-btn-checkbox" v-if="order_max === 1">'
+ '<input type="checkbox" value="1" :checked="!!amount_selected" @change="amount_selected = $event.target.checked" :name="input_name"' + '<input ref="quantity" type="checkbox" value="1" :name="input_name"'
+ ' v-bind:aria-label="label_select_item"' + ' v-bind:aria-label="label_select_item"'
+ '>' + '>'
+ '<span class="pretix-widget-icon-cart" aria-hidden="true"></span> ' + strings.select + '<span class="pretix-widget-icon-cart" aria-hidden="true"></span> ' + strings.select
+ '</label>' + '</label>'
+ '<label class="pretix-widget-item-count-single-label" v-else-if="order_max === 1">' + '<div class="pretix-widget-item-count-group" v-else role="group" v-bind:aria-label="item.name">'
+ '<input type="checkbox" value="1" :checked="!!amount_selected" @change="amount_selected = $event.target.checked" :name="input_name"' + '<button type="button" @click.prevent.stop="on_step" data-step="-1" v-bind:data-controls="\'input_\' + input_name" class="pretix-widget-btn-default pretix-widget-item-count-dec" v-bind:aria-label="dec_label"><span>-</span></button>'
+ ' v-bind:aria-label="label_select_item"' + '<input ref="quantity" type="number" inputmode="numeric" pattern="\d*" class="pretix-widget-item-count-multiple" placeholder="0" min="0"'
+ '>' + ' :max="order_max" :name="input_name" :id="\'input_\' + input_name"'
+ '</label>' + ' v-bind:aria-labelledby="aria_labelledby"'
+ '<div :class="count_group_classes" v-else role="group" v-bind:aria-label="item.name">'
+ '<button v-if="!$root.use_native_spinners" type="button" @click.prevent.stop="on_step" data-step="-1" v-bind:data-controls="\'input_\' + input_name" class="pretix-widget-btn-default pretix-widget-item-count-dec" v-bind:aria-label="dec_label"><span>-</span></button>'
+ '<input type="number" inputmode="numeric" pattern="\d*" class="pretix-widget-item-count-multiple" placeholder="0" min="0"'
+ ' v-model="amount_selected" :max="order_max" :name="input_name" :id="\'input_\' + input_name"'
+ ' v-bind:aria-labelledby="aria_labelledby" ref="quantity"'
+ ' >' + ' >'
+ '<button v-if="!$root.use_native_spinners" type="button" @click.prevent.stop="on_step" data-step="1" v-bind:data-controls="\'input_\' + input_name" class="pretix-widget-btn-default pretix-widget-item-count-inc" v-bind:aria-label="inc_label"><span>+</span></button>' + '<button type="button" @click.prevent.stop="on_step" data-step="1" v-bind:data-controls="\'input_\' + input_name" class="pretix-widget-btn-default pretix-widget-item-count-inc" v-bind:aria-label="inc_label"><span>+</span></button>'
+ '</div>' + '</div>'
+ '</div>' + '</div>'
+ '</div>'), + '</div>'),
@@ -253,15 +252,17 @@ Vue.component('availbox', {
variation: Object variation: Object
}, },
mounted: function() { mounted: function() {
if (this.item.has_variations) { if (this.$root.itemnum === 1 && !this.$root.has_seating_plan ? 1 : 0) {
this.$set(this.variation, 'amount_selected', 0); this.$refs.quantity.value = 1;
} else { if (this.order_max === 1) {
// Automatically set the only available item to be selected. this.$refs.quantity.checked = true;
this.$set(this.item, 'amount_selected', this.$root.itemnum === 1 && !this.$root.has_seating_plan ? 1 : 0); }
} }
this.$root.$emit('amounts_changed')
}, },
computed: { computed: {
voucher_jump_link: function () {
return '#' + this.$root.html_id + '-voucher-input';
},
aria_labelledby: function () { aria_labelledby: function () {
return this.$root.html_id + '-item-label-' + this.item.id; return this.$root.html_id + '-item-label-' + this.item.id;
}, },
@@ -271,11 +272,6 @@ Vue.component('availbox', {
inc_label: function () { inc_label: function () {
return '+ ' + (this.item.has_variations ? this.variation.value : this.item.name) + ': ' + strings.quantity_inc; return '+ ' + (this.item.has_variations ? this.variation.value : this.item.name) + ': ' + strings.quantity_inc;
}, },
count_group_classes: function () {
return {
'pretix-widget-item-count-group': !this.$root.use_native_spinners
}
},
unavailability_reason_message: function () { unavailability_reason_message: function () {
var reason = this.item.current_unavailability_reason || this.variation?.current_unavailability_reason; var reason = this.item.current_unavailability_reason || this.variation?.current_unavailability_reason;
if (reason) { if (reason) {
@@ -283,29 +279,6 @@ Vue.component('availbox', {
} }
return ""; return "";
}, },
amount_selected: {
cache: false,
get: function () {
var selected = this.item.has_variations ? this.variation.amount_selected : this.item.amount_selected
if (selected === 0) return undefined;
return selected
},
set: function (value) {
// Unary operator to force boolean to integer conversion, as the HTML form submission
// needs the value to be integer for all products.
value = (+value);
if (this.item.has_variations) {
this.variation.amount_selected = value;
} else {
this.item.amount_selected = value;
}
if (this.$refs.quantity) {
// manually set value on quantity as on reload somehow v-model binding breaks
this.$refs.quantity.value = value;
}
this.$root.$emit("amounts_changed")
}
},
label_select_item: function () { label_select_item: function () {
return this.item.has_variations return this.item.has_variations
? strings.select_variant.replace("%s", this.variation.value) ? strings.select_variant.replace("%s", this.variation.value)
@@ -341,14 +314,14 @@ Vue.component('availbox', {
} }
}, },
methods: { methods: {
focus_voucher_field: function () {
this.$root.$emit('focus_voucher_field')
},
on_step: function (e) { on_step: function (e) {
var t = e.target.tagName == 'BUTTON' ? e.target : e.target.closest('button'); var t = e.target.tagName == 'BUTTON' ? e.target : e.target.closest('button');
var step = parseFloat(t.getAttribute("data-step")); var step = parseFloat(t.getAttribute("data-step"));
var controls = document.getElementById(t.getAttribute("data-controls")); var controls = document.getElementById(t.getAttribute("data-controls"));
this.amount_selected = Math.max(controls.min, Math.min(controls.max || Number.MAX_SAFE_INTEGER, (this.amount_selected || 0) + step)); this.$refs.quantity.value = Math.max(controls.min, Math.min(controls.max || Number.MAX_SAFE_INTEGER, (parseInt(this.$refs.quantity.value || "0")) + step));
this.$refs.quantity.dispatchEvent(new CustomEvent("change", {
bubbles: true,
}));
} }
} }
}); });
@@ -537,12 +510,7 @@ Vue.component('item', {
+ '<div class="pretix-widget-item-info-col">' + '<div class="pretix-widget-item-info-col">'
+ '<a :href="item.picture_fullsize" v-if="item.picture" class="pretix-widget-item-picture-link" @click.prevent.stop="lightbox"><img :src="item.picture" class="pretix-widget-item-picture" :alt="picture_alt_text"></a>' + '<a :href="item.picture_fullsize" v-if="item.picture" class="pretix-widget-item-picture-link" @click.prevent.stop="lightbox"><img :src="item.picture" class="pretix-widget-item-picture" :alt="picture_alt_text"></a>'
+ '<div class="pretix-widget-item-title-and-description">' + '<div class="pretix-widget-item-title-and-description">'
+ '<a v-if="item.has_variations && show_toggle" :id="item_label_id" role="heading" v-bind:aria-level="headingLevel" class="pretix-widget-item-title" :href="\'#\' + item.id + \'-variants\'"' + '<strong class="pretix-widget-item-title" :id="item_label_id" role="heading" v-bind:aria-level="headingLevel">{{ item.name }}</strong>'
+ ' @click.prevent.stop="expand"'
+ '>'
+ '{{ item.name }}'
+ '</a>'
+ '<strong v-else class="pretix-widget-item-title" :id="item_label_id" role="heading" v-bind:aria-level="headingLevel">{{ item.name }}</strong>'
+ '<div class="pretix-widget-item-description" :id="item_desc_id" v-if="item.description" v-html="item.description"></div>' + '<div class="pretix-widget-item-description" :id="item_desc_id" v-if="item.description" v-html="item.description"></div>'
+ '<p class="pretix-widget-item-meta" v-if="item.order_min && item.order_min > 1">' + '<p class="pretix-widget-item-meta" v-if="item.order_min && item.order_min > 1">'
+ '<small>{{ min_order_str }}</small>' + '<small>{{ min_order_str }}</small>'
@@ -566,8 +534,8 @@ Vue.component('item', {
// Availability // Availability
+ '<div class="pretix-widget-item-availability-col">' + '<div class="pretix-widget-item-availability-col">'
+ '<a class="pretix-widget-collapse-indicator" v-if="show_toggle" :href="\'#\' + item.id + \'-variants\'" @click.prevent.stop="expand" role="button" tabindex="0"' + '<button type="button" class="pretix-widget-collapse-indicator" v-if="show_toggle" @click.prevent.stop="expand"'
+ ' v-bind:aria-expanded="expanded ? \'true\': \'false\'" v-bind:aria-controls="item.id + \'-variants\'">{{ variationsToggleLabel }}</a>' + ' v-bind:aria-expanded="expanded ? \'true\': \'false\'" v-bind:aria-controls="item.id + \'-variants\'" v-bind:aria-describedby="item_desc_id">{{ variationsToggleLabel }}</button>'
+ '<availbox v-if="!item.has_variations" :item="item"></availbox>' + '<availbox v-if="!item.has_variations" :item="item"></availbox>'
+ '</div>' + '</div>'
@@ -622,7 +590,7 @@ Vue.component('item', {
image: this.item.picture_fullsize, image: this.item.picture_fullsize,
description: this.item.name, description: this.item.name,
} }
} },
}, },
computed: { computed: {
classObject: function () { classObject: function () {
@@ -855,53 +823,54 @@ var shared_loading_fragment = (
); );
var shared_iframe_fragment = ( var shared_iframe_fragment = (
'<div :class="frameClasses" role="dialog" aria-modal="true" aria-label="'+strings.checkout+'">' '<dialog :class="frameClasses" role="alertdialog" aria-label="'+strings.checkout+'" @close="close" @cancel="cancel">'
+ '<div class="pretix-widget-frame-loading" v-show="$root.frame_loading">' + '<div class="pretix-widget-frame-loading" v-show="$root.frame_loading">'
+ '<svg width="256" height="256" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path class="pretix-widget-primary-color" d="M1152 896q0-106-75-181t-181-75-181 75-75 181 75 181 181 75 181-75 75-181zm512-109v222q0 12-8 23t-20 13l-185 28q-19 54-39 91 35 50 107 138 10 12 10 25t-9 23q-27 37-99 108t-94 71q-12 0-26-9l-138-108q-44 23-91 38-16 136-29 186-7 28-36 28h-222q-14 0-24.5-8.5t-11.5-21.5l-28-184q-49-16-90-37l-141 107q-10 9-25 9-14 0-25-11-126-114-165-168-7-10-7-23 0-12 8-23 15-21 51-66.5t54-70.5q-27-50-41-99l-183-27q-13-2-21-12.5t-8-23.5v-222q0-12 8-23t19-13l186-28q14-46 39-92-40-57-107-138-10-12-10-24 0-10 9-23 26-36 98.5-107.5t94.5-71.5q13 0 26 10l138 107q44-23 91-38 16-136 29-186 7-28 36-28h222q14 0 24.5 8.5t11.5 21.5l28 184q49 16 90 37l142-107q9-9 24-9 13 0 25 10 129 119 165 170 7 8 7 22 0 12-8 23-15 21-51 66.5t-54 70.5q26 50 41 98l183 28q13 2 21 12.5t8 23.5z"/></svg>' + '<svg width="256" height="256" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path class="pretix-widget-primary-color" d="M1152 896q0-106-75-181t-181-75-181 75-75 181 75 181 181 75 181-75 75-181zm512-109v222q0 12-8 23t-20 13l-185 28q-19 54-39 91 35 50 107 138 10 12 10 25t-9 23q-27 37-99 108t-94 71q-12 0-26-9l-138-108q-44 23-91 38-16 136-29 186-7 28-36 28h-222q-14 0-24.5-8.5t-11.5-21.5l-28-184q-49-16-90-37l-141 107q-10 9-25 9-14 0-25-11-126-114-165-168-7-10-7-23 0-12 8-23 15-21 51-66.5t54-70.5q-27-50-41-99l-183-27q-13-2-21-12.5t-8-23.5v-222q0-12 8-23t19-13l186-28q14-46 39-92-40-57-107-138-10-12-10-24 0-10 9-23 26-36 98.5-107.5t94.5-71.5q13 0 26 10l138 107q44-23 91-38 16-136 29-186 7-28 36-28h222q14 0 24.5 8.5t11.5 21.5l28 184q49 16 90 37l142-107q9-9 24-9 13 0 25 10 129 119 165 170 7 8 7 22 0 12-8 23-15 21-51 66.5t-54 70.5q26 50 41 98l183 28q13 2 21 12.5t8 23.5z"/></svg>'
+ '<p :class="cancelBlockedClasses"><strong>'+strings.cancel_blocked+'</strong></p>'
+ '</div>' + '</div>'
+ '<div class="pretix-widget-frame-inner" ref="frame-container" v-show="$root.frame_shown">' + '<div class="pretix-widget-frame-inner" ref="frame-container" v-show="$root.frame_shown">'
+ '<div class="pretix-widget-frame-close"><a href="#" @click.prevent.stop="close" role="button" aria-label="'+strings.close+'">' + '<form class="pretix-widget-frame-close" method="dialog"><button aria-label="'+strings.close_checkout+'" autofocus="autofocus">'
+ '<svg alt="'+strings.close+'" height="16" viewBox="0 0 512 512" width="16" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M437.5,386.6L306.9,256l130.6-130.6c14.1-14.1,14.1-36.8,0-50.9c-14.1-14.1-36.8-14.1-50.9,0L256,205.1L125.4,74.5 c-14.1-14.1-36.8-14.1-50.9,0c-14.1,14.1-14.1,36.8,0,50.9L205.1,256L74.5,386.6c-14.1,14.1-14.1,36.8,0,50.9 c14.1,14.1,36.8,14.1,50.9,0L256,306.9l130.6,130.6c14.1,14.1,36.8,14.1,50.9,0C451.5,423.4,451.5,400.6,437.5,386.6z"/></svg>' + '<svg alt="'+strings.close+'" height="16" viewBox="0 0 512 512" width="16" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M437.5,386.6L306.9,256l130.6-130.6c14.1-14.1,14.1-36.8,0-50.9c-14.1-14.1-36.8-14.1-50.9,0L256,205.1L125.4,74.5 c-14.1-14.1-36.8-14.1-50.9,0c-14.1,14.1-14.1,36.8,0,50.9L205.1,256L74.5,386.6c-14.1,14.1-14.1,36.8,0,50.9 c14.1,14.1,36.8,14.1,50.9,0L256,306.9l130.6,130.6c14.1,14.1,36.8,14.1,50.9,0C451.5,423.4,451.5,400.6,437.5,386.6z"/></svg>'
+ '</a></div>' + '</button></form>'
+ '<iframe frameborder="0" width="650" height="650" @load="iframeLoaded" ' + '<iframe frameborder="0" width="650" height="650" @load="iframeLoaded" '
+ ' :name="$root.parent.widget_id" src="about:blank" v-once' + ' :name="$root.parent.widget_id" src="about:blank" v-once'
+ ' allow="autoplay *; camera *; fullscreen *; payment *"' + ' allow="autoplay *; camera *; fullscreen *; payment *"'
+ ' title="'+strings.checkout+'"' + ' title="'+strings.checkout+'"'
+ ' referrerpolicy="origin">' + ' referrerpolicy="origin">'
+ 'Please enable frames in your browser!' + 'Please enable frames in your browser!'
+ '</iframe>' + '</iframe>'
+ '</div>'
+ '</div>' + '</div>'
+ '</dialog>'
); );
var shared_alert_fragment = ( var shared_alert_fragment = (
'<div :class="alertClasses" role="alertdialog" v-bind:aria-labelledby="$root.parent.html_id + \'-error-message\'">' '<dialog :class="alertClasses" role="alertdialog" v-bind:aria-labelledby="$root.parent.html_id + \'-error-message\'" @close="errorClose">'
+ '<transition name="bounce" @after-enter="focusButton">' + '<form class="pretix-widget-alert-box" method="dialog">'
+ '<div class="pretix-widget-alert-box" v-if="$root.error_message">'
+ '<p :id="$root.parent.html_id + \'-error-message\'">{{ $root.error_message }}</p>' + '<p :id="$root.parent.html_id + \'-error-message\'">{{ $root.error_message }}</p>'
+ '<p><button v-if="$root.error_url_after" @click.prevent.stop="errorContinue">' + strings.continue + '</button>' + '<p><button v-if="$root.error_url_after" value="continue" autofocus v-bind:aria-describedby="$root.parent.html_id + \'-error-message\'">' + strings.continue + '</button>'
+ '<button v-else @click.prevent.stop="errorClose">' + strings.close + '</button></p>' + '<button v-else autofocus v-bind:aria-describedby="$root.parent.html_id + \'-error-message\'">' + strings.close + '</button></p>'
+ '</div>' + '</form>'
+ '<transition name="bounce">'
+ '<svg v-if="$root.error_message" width="64" height="64" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg" class="pretix-widget-alert-icon"><path style="fill:#ffffff;" d="M 599.86438,303.72882 H 1203.5254 V 1503.4576 H 599.86438 Z" /><path class="pretix-widget-primary-color" d="M896 128q209 0 385.5 103t279.5 279.5 103 385.5-103 385.5-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103zm128 1247v-190q0-14-9-23.5t-22-9.5h-192q-13 0-23 10t-10 23v190q0 13 10 23t23 10h192q13 0 22-9.5t9-23.5zm-2-344l18-621q0-12-10-18-10-8-24-8h-220q-14 0-24 8-10 6-10 18l17 621q0 10 10 17.5t24 7.5h185q14 0 23.5-7.5t10.5-17.5z"/></svg>'
+ '</transition>' + '</transition>'
+ '<svg width="64" height="64" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg" class="pretix-widget-alert-icon"><path style="fill:#ffffff;" d="M 599.86438,303.72882 H 1203.5254 V 1503.4576 H 599.86438 Z" /><path class="pretix-widget-primary-color" d="M896 128q209 0 385.5 103t279.5 279.5 103 385.5-103 385.5-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103zm128 1247v-190q0-14-9-23.5t-22-9.5h-192q-13 0-23 10t-10 23v190q0 13 10 23t23 10h192q13 0 22-9.5t9-23.5zm-2-344l18-621q0-12-10-18-10-8-24-8h-220q-14 0-24 8-10 6-10 18l17 621q0 10 10 17.5t24 7.5h185q14 0 23.5-7.5t10.5-17.5z"/></svg>' + '</dialog>'
+ '</div>'
); );
var shared_lightbox_fragment = ( var shared_lightbox_fragment = (
'<div :class="lightboxClasses" role="dialog" aria-modal="true" v-if="$root.lightbox" @click="lightboxClose">' '<dialog :class="lightboxClasses" role="alertdialog" @close="lightboxClose">'
+ '<div class="pretix-widget-lightbox-loading" v-if="$root.lightbox?.loading">' + '<div class="pretix-widget-lightbox-loading" v-if="$root.lightbox?.loading">'
+ '<svg width="256" height="256" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path class="pretix-widget-primary-color" d="M1152 896q0-106-75-181t-181-75-181 75-75 181 75 181 181 75 181-75 75-181zm512-109v222q0 12-8 23t-20 13l-185 28q-19 54-39 91 35 50 107 138 10 12 10 25t-9 23q-27 37-99 108t-94 71q-12 0-26-9l-138-108q-44 23-91 38-16 136-29 186-7 28-36 28h-222q-14 0-24.5-8.5t-11.5-21.5l-28-184q-49-16-90-37l-141 107q-10 9-25 9-14 0-25-11-126-114-165-168-7-10-7-23 0-12 8-23 15-21 51-66.5t54-70.5q-27-50-41-99l-183-27q-13-2-21-12.5t-8-23.5v-222q0-12 8-23t19-13l186-28q14-46 39-92-40-57-107-138-10-12-10-24 0-10 9-23 26-36 98.5-107.5t94.5-71.5q13 0 26 10l138 107q44-23 91-38 16-136 29-186 7-28 36-28h222q14 0 24.5 8.5t11.5 21.5l28 184q49 16 90 37l142-107q9-9 24-9 13 0 25 10 129 119 165 170 7 8 7 22 0 12-8 23-15 21-51 66.5t-54 70.5q26 50 41 98l183 28q13 2 21 12.5t8 23.5z"/></svg>' + '<svg width="256" height="256" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path class="pretix-widget-primary-color" d="M1152 896q0-106-75-181t-181-75-181 75-75 181 75 181 181 75 181-75 75-181zm512-109v222q0 12-8 23t-20 13l-185 28q-19 54-39 91 35 50 107 138 10 12 10 25t-9 23q-27 37-99 108t-94 71q-12 0-26-9l-138-108q-44 23-91 38-16 136-29 186-7 28-36 28h-222q-14 0-24.5-8.5t-11.5-21.5l-28-184q-49-16-90-37l-141 107q-10 9-25 9-14 0-25-11-126-114-165-168-7-10-7-23 0-12 8-23 15-21 51-66.5t54-70.5q-27-50-41-99l-183-27q-13-2-21-12.5t-8-23.5v-222q0-12 8-23t19-13l186-28q14-46 39-92-40-57-107-138-10-12-10-24 0-10 9-23 26-36 98.5-107.5t94.5-71.5q13 0 26 10l138 107q44-23 91-38 16-136 29-186 7-28 36-28h222q14 0 24.5 8.5t11.5 21.5l28 184q49 16 90 37l142-107q9-9 24-9 13 0 25 10 129 119 165 170 7 8 7 22 0 12-8 23-15 21-51 66.5t-54 70.5q26 50 41 98l183 28q13 2 21 12.5t8 23.5z"/></svg>'
+ '</div>' + '</div>'
+ '<div class="pretix-widget-lightbox-inner" @click.stop="">' + '<div class="pretix-widget-lightbox-inner" v-if="$root.lightbox">'
+ '<form class="pretix-widget-lightbox-close" method="dialog"><button aria-label="'+strings.close+'" autofocus="autofocus">'
+ '<svg alt="'+strings.close+'" height="16" viewBox="0 0 512 512" width="16" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M437.5,386.6L306.9,256l130.6-130.6c14.1-14.1,14.1-36.8,0-50.9c-14.1-14.1-36.8-14.1-50.9,0L256,205.1L125.4,74.5 c-14.1-14.1-36.8-14.1-50.9,0c-14.1,14.1-14.1,36.8,0,50.9L205.1,256L74.5,386.6c-14.1,14.1-14.1,36.8,0,50.9 c14.1,14.1,36.8,14.1,50.9,0L256,306.9l130.6,130.6c14.1,14.1,36.8,14.1,50.9,0C451.5,423.4,451.5,400.6,437.5,386.6z"/></svg>'
+ '</button></form>'
+ '<figure class="pretix-widget-lightbox-image">' + '<figure class="pretix-widget-lightbox-image">'
+ '<img :src="$root.lightbox.image" :alt="$root.lightbox.description" @load="lightboxLoaded" ref="lightboxImage" crossorigin>' + '<img :src="$root.lightbox.image" :alt="$root.lightbox.description" @load="lightboxLoaded" ref="lightboxImage" crossorigin>'
+ '<figcaption v-if="$root.lightbox.description">{{$root.lightbox.description}}</figcaption>' + '<figcaption v-if="$root.lightbox.description">{{$root.lightbox.description}}</figcaption>'
+ '</figure>' + '</figure>'
+ '<button type="button" class="pretix-widget-lightbox-close" @click="lightboxClose" aria-label="'+strings.close+'">'
+ '<svg alt="'+strings.close+'" height="16" viewBox="0 0 512 512" width="16" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M437.5,386.6L306.9,256l130.6-130.6c14.1-14.1,14.1-36.8,0-50.9c-14.1-14.1-36.8-14.1-50.9,0L256,205.1L125.4,74.5 c-14.1-14.1-36.8-14.1-50.9,0c-14.1,14.1-14.1,36.8,0,50.9L205.1,256L74.5,386.6c-14.1,14.1-14.1,36.8,0,50.9 c14.1,14.1,36.8,14.1,50.9,0L256,306.9l130.6,130.6c14.1,14.1,36.8,14.1,50.9,0C451.5,423.4,451.5,400.6,437.5,386.6z"/></svg>'
+ '</button>'
+ '</div>' + '</div>'
+ '</div>' + '</dialog>'
); );
Vue.component('pretix-overlay', { Vue.component('pretix-overlay', {
@@ -911,6 +880,11 @@ Vue.component('pretix-overlay', {
+ shared_lightbox_fragment + shared_lightbox_fragment
+ '</div>' + '</div>'
), ),
data: function () {
return {
cancelBlocked: false,
}
},
watch: { watch: {
'$root.lightbox': function (newValue, oldValue) { '$root.lightbox': function (newValue, oldValue) {
if (newValue) { if (newValue) {
@@ -918,18 +892,24 @@ Vue.component('pretix-overlay', {
this.$set(newValue, "loading", true); this.$set(newValue, "loading", true);
} }
if (!oldValue) { if (!oldValue) {
window.addEventListener('keyup', this.lightboxCloseOnKeyup); this.$el?.querySelector(".pretix-widget-lightbox-holder").showModal();
} }
} else {
window.removeEventListener('keyup', this.lightboxCloseOnKeyup);
} }
} },
'$root.error_message': function (newValue, oldValue) {
if (newValue) {
if (!oldValue) {
this.$el?.querySelector(".pretix-widget-alert-holder").showModal();
}
}
},
}, },
computed: { computed: {
frameClasses: function () { frameClasses: function () {
return { return {
'pretix-widget-frame-holder': true, 'pretix-widget-frame-holder': true,
'pretix-widget-frame-shown': this.$root.frame_shown || this.$root.frame_loading, 'pretix-widget-frame-shown': this.$root.frame_shown || this.$root.frame_loading,
'pretix-widget-frame-isloading': this.$root.frame_loading,
}; };
}, },
alertClasses: function () { alertClasses: function () {
@@ -945,54 +925,67 @@ Vue.component('pretix-overlay', {
'pretix-widget-lightbox-isloading': this.$root.lightbox?.loading, 'pretix-widget-lightbox-isloading': this.$root.lightbox?.loading,
}; };
}, },
}, cancelBlockedClasses: function () {
methods: { return {
lightboxCloseOnKeyup: function (event) { 'pretix-widget-visibility-hidden': !this.cancelBlocked,
if (event.keyCode === 27) {
// abort on ESC-key
this.lightboxClose();
} }
}, },
},
methods: {
lightboxClose: function () { lightboxClose: function () {
this.$root.lightbox = null; this.$root.lightbox = null;
}, },
lightboxLoaded: function () { lightboxLoaded: function () {
this.$root.lightbox.loading = false; this.$root.lightbox.loading = false;
}, },
errorClose: function () { errorClose: function (e) {
var dialog = e.target;
if (dialog.returnValue == "continue" && this.$root.error_url_after) {
if (this.$root.error_url_after_new_tab) {
window.open(this.$root.error_url_after);
} else if (this.$root.overlay) {
this.$root.overlay.frame_src = this.$root.error_url_after;
this.$root.frame_loading = true;
}
}
this.$root.error_message = null; this.$root.error_message = null;
this.$root.error_url_after = null; this.$root.error_url_after = null;
this.$root.error_url_after_new_tab = false; this.$root.error_url_after_new_tab = false;
}, },
errorContinue: function () { close: function (e) {
if (this.$root.error_url_after_new_tab) { if (this.$root.frame_loading) {
window.open(this.$root.error_url_after); // Chrome does not allow blocking dialog.cancel event more than once
// => wiggle the loading-element and re-open the modal
this.cancel(e);
e.target.showModal();
return; return;
} }
this.$root.overlay.frame_src = this.$root.error_url_after;
this.$root.frame_loading = true;
this.$root.error_message = null;
this.$root.error_url_after = null;
},
close: function () {
this.$root.frame_shown = false; this.$root.frame_shown = false;
this.$root.parent.frame_dismissed = true; this.$root.parent.frame_dismissed = true;
this.$root.frame_src = ""; this.$root.frame_src = "";
this.$root.parent.reload(); this.$root.parent.reload();
this.$root.parent.trigger_close_callback(); this.$root.parent.trigger_close_callback();
}, },
cancel: function (e) {
// do not allow to cancel while frame is loading as we cannot abort the operation
if (this.$root.frame_loading) {
e.preventDefault();
e.target.addEventListener("animationend", function () {
e.target.classList.remove("pretix-widget-shake-once");
}, {once: true});
e.target.classList.add("pretix-widget-shake-once");
this.cancelBlocked = true;
}
},
iframeLoaded: function () { iframeLoaded: function () {
if (this.$root.frame_loading) { if (this.$root.frame_loading) {
this.$root.frame_loading = false; this.$root.frame_loading = false;
this.cancelBlocked = false;
if (this.$root.frame_src) { if (this.$root.frame_src) {
this.$root.frame_shown = true; this.$root.frame_shown = true;
} }
} }
}, },
focusButton: function () {
this.$el.querySelector(".pretix-widget-alert-box button").focus();
},
} }
}); });
@@ -1033,13 +1026,11 @@ Vue.component('pretix-widget-event-form', {
+ '<div class="pretix-widget-error-message" v-if="$root.error">{{ $root.error }}</div>' + '<div class="pretix-widget-error-message" v-if="$root.error">{{ $root.error }}</div>'
// Resume cart // Resume cart
+ '<div class="pretix-widget-info-message pretix-widget-clickable"' + '<div class="pretix-widget-info-message pretix-widget-clickable" v-if="$root.cart_exists">'
+ ' v-if="$root.cart_exists">' + '<span :id="id_cart_exists_msg">' + strings['cart_exists'] + '</span>'
+ '<button @click.prevent.stop="$parent.resume" class="pretix-widget-resume-button" type="button" v-bind:aria-describedby="id_cart_exists_msg">' + '<button @click.prevent.stop="$parent.resume" class="pretix-widget-resume-button" type="button" v-bind:aria-describedby="id_cart_exists_msg">'
+ strings['resume_checkout'] + strings['resume_checkout']
+ '</button>' + '</button>'
+ '<span :id="id_cart_exists_msg">' + strings['cart_exists'] + '</span>'
+ '<div class="pretix-widget-clear"></div>'
+ '</div>' + '</div>'
// Seating plan // Seating plan
@@ -1067,7 +1058,10 @@ Vue.component('pretix-widget-event-form', {
// Buy button // Buy button
+ '<div class="pretix-widget-action" v-if="$root.display_add_to_cart">' + '<div class="pretix-widget-action" v-if="$root.display_add_to_cart">'
+ '<button type="submit">{{ this.buy_label }}</button>' + '<button v-if="!this.$root.cart_exists || this.is_items_selected" type="submit" v-bind:aria-describedby="id_cart_exists_msg">{{ buy_label }}</button>'
+ '<button v-else @click.prevent.stop="$parent.resume" type="button" v-bind:aria-describedby="id_cart_exists_msg">'
+ strings['resume_checkout']
+ '</button>'
+ '</div>' + '</div>'
+ '</form>' + '</form>'
@@ -1079,7 +1073,7 @@ Vue.component('pretix-widget-event-form', {
+ '<h3 class="pretix-widget-voucher-headline" :id="aria_labelledby">'+ strings['redeem_voucher'] +'</h3>' + '<h3 class="pretix-widget-voucher-headline" :id="aria_labelledby">'+ strings['redeem_voucher'] +'</h3>'
+ '<div v-if="$root.voucher_explanation_text" class="pretix-widget-voucher-text" v-html="$root.voucher_explanation_text"></div>' + '<div v-if="$root.voucher_explanation_text" class="pretix-widget-voucher-text" v-html="$root.voucher_explanation_text"></div>'
+ '<div class="pretix-widget-voucher-input-wrap">' + '<div class="pretix-widget-voucher-input-wrap">'
+ '<input class="pretix-widget-voucher-input" ref="voucherinput" type="text" v-model="$parent.voucher" name="voucher" placeholder="'+strings.voucher_code+'" v-bind:aria-labelledby="aria_labelledby">' + '<input :id="id_voucher_input" class="pretix-widget-voucher-input" ref="voucherinput" type="text" v-model="$parent.voucher" name="voucher" placeholder="'+strings.voucher_code+'" v-bind:aria-labelledby="aria_labelledby">'
+ '</div>' + '</div>'
+ '<input type="hidden" v-for="p in hiddenParams" :name="p[0]" :value="p[1]" />' + '<input type="hidden" v-for="p in hiddenParams" :name="p[0]" :value="p[1]" />'
+ '<div class="pretix-widget-voucher-button-wrap">' + '<div class="pretix-widget-voucher-button-wrap">'
@@ -1091,14 +1085,32 @@ Vue.component('pretix-widget-event-form', {
+ '</div>' + '</div>'
), ),
data: function () {
return {
is_items_selected: false,
}
},
watch: {
'$root.overlay.frame_shown': function (newValue) {
if (!newValue) {
this.$refs.form.reset();
this.calc_items_selected();
}
}
},
mounted: function() { mounted: function() {
this.$root.$on('focus_voucher_field', this.focus_voucher_field) this.$root.$on('focus_voucher_field', this.focus_voucher_field);
this.$refs.form.addEventListener("change", this.calc_items_selected);
}, },
beforeDestroy: function() { beforeDestroy: function() {
this.$root.$off('focus_voucher_field', this.focus_voucher_field) this.$root.$off('focus_voucher_field', this.focus_voucher_field);
this.$refs.form.removeEventListener("change", this.calc_items_selected);
}, },
computed: { computed: {
aria_labelledby: function() { id_voucher_input: function () {
return this.$root.html_id + '-voucher-input';
},
aria_labelledby: function () {
return this.$root.html_id + '-voucher-headline'; return this.$root.html_id + '-voucher-headline';
}, },
display_event_info: function () { display_event_info: function () {
@@ -1143,10 +1155,6 @@ Vue.component('pretix-widget-event-form', {
}, },
}, },
methods: { methods: {
focus_voucher_field: function() {
this.$refs.voucherinput.scrollIntoView(false)
this.$refs.voucherinput.focus()
},
back_to_list: function() { back_to_list: function() {
this.$root.target_url = this.$root.parent_stack.pop(); this.$root.target_url = this.$root.parent_stack.pop();
this.$root.error = null; this.$root.error = null;
@@ -1173,32 +1181,26 @@ Vue.component('pretix-widget-event-form', {
$el.focus(); $el.focus();
}); });
}, },
calc_items_selected: function () {
this.is_items_selected = [...this.$refs.form.querySelectorAll("input[type=checkbox], input[type=radio]")].some(function(element) {
return element.checked;
}) || [...this.$refs.form.querySelectorAll(".pretix-widget-item-count-group input")].some(function(element) {
return parseInt(element.value || "0") > 0;
});
},
} }
}); });
Vue.component('pretix-widget-event-list-filter-field', { Vue.component('pretix-widget-event-list-filter-field', {
template: ('<div class="pretix-widget-event-list-filter-field">' template: ('<div class="pretix-widget-event-list-filter-field">'
+ '<label :for="id">{{ field.label }}</label>' + '<label :for="id">{{ field.label }}</label>'
+ '<select :id="id" :name="field.key" @change="onChange($event)" :value="currentValue">' + '<select :id="id" :name="field.key" :value="currentValue">'
+ '<option v-for="choice in field.choices" :value="choice[0]">{{ choice[1] }}</option>' + '<option v-for="choice in field.choices" :value="choice[0]">{{ choice[1] }}</option>'
+ '</select>' + '</select>'
+ '</div>'), + '</div>'),
props: { props: {
field: Object field: Object
}, },
methods: {
onChange: function(event) {
var filterParams = new URLSearchParams(this.$root.filter);
if (event.target.value) {
filterParams.set(this.field.key, event.target.value);
} else {
filterParams.delete(this.field.key);
}
this.$root.filter = filterParams.toString();
this.$root.loading++;
this.$root.reload();
},
},
computed: { computed: {
id: function () { id: function () {
return widget_id + "_" + this.field.key; return widget_id + "_" + this.field.key;
@@ -1211,9 +1213,29 @@ Vue.component('pretix-widget-event-list-filter-field', {
}); });
Vue.component('pretix-widget-event-list-filter-form', { Vue.component('pretix-widget-event-list-filter-form', {
template: ('<div class="pretix-widget-event-list-filter-form">' template: ('<form ref="filterform" class="pretix-widget-event-list-filter-form" @submit="onSubmit">'
+ '<pretix-widget-event-list-filter-field v-for="field in $root.meta_filter_fields" :field="field" :key="field.key"></pretix-widget-event-list-filter-field>' + '<fieldset class="pretix-widget-event-list-filter-fieldset">'
+ '</div>'), + '<legend>' + strings.filter_events_by + '</legend>'
+ '<pretix-widget-event-list-filter-field v-for="field in $root.meta_filter_fields" :field="field" :key="field.key"></pretix-widget-event-list-filter-field>'
+ '<button>' + strings.filter + '</button>'
+ '</fieldset>'
+ '</form>'),
methods: {
onSubmit: function(e) {
e.preventDefault();
var formData = new FormData(this.$refs.filterform);
var filterParams = new URLSearchParams(formData);
formData.forEach(function (value, key) {
if (value == "") {
filterParams.delete(key);
}
});
this.$root.filter = filterParams.toString();
this.$root.loading++;
this.$root.reload();
},
},
}); });
Vue.component('pretix-widget-event-list-entry', { Vue.component('pretix-widget-event-list-entry', {
@@ -1740,7 +1762,7 @@ Vue.component('pretix-widget', {
return { return {
'pretix-widget': true, 'pretix-widget': true,
'pretix-widget-mobile': this.mobile, 'pretix-widget-mobile': this.mobile,
'pretix-widget-use-custom-spinners': !this.$root.use_native_spinners 'pretix-widget-use-custom-spinners': true,
}; };
} }
} }
@@ -1902,7 +1924,6 @@ var shared_root_methods = {
root.categories = data.items_by_category; root.categories = data.items_by_category;
root.currency = data.currency; root.currency = data.currency;
root.display_net_prices = data.display_net_prices; root.display_net_prices = data.display_net_prices;
root.use_native_spinners = data.use_native_spinners;
root.voucher_explanation_text = data.voucher_explanation_text; root.voucher_explanation_text = data.voucher_explanation_text;
root.error = data.error; root.error = data.error;
root.display_add_to_cart = data.display_add_to_cart; root.display_add_to_cart = data.display_add_to_cart;
@@ -2161,28 +2182,11 @@ var create_overlay = function (app) {
// show loading spinner only when previously no frame_src was set // show loading spinner only when previously no frame_src was set
if (newValue && !oldValue) { if (newValue && !oldValue) {
this.frame_loading = true; this.frame_loading = true;
this.$el?.querySelector('dialog.pretix-widget-frame-holder').showModal();
} }
// to close and unload the iframe, frame_src can be empty -> make it valid HTML with about:blank // to close and unload the iframe, frame_src can be empty -> make it valid HTML with about:blank
this.$el.querySelector("iframe").src = newValue || "about:blank"; this.$el.querySelector("iframe").src = newValue || "about:blank";
}, },
frame_shown: function (newValue) {
if (newValue) {
this.prevActiveElement = document.activeElement;
var btn = this.$el?.querySelector(".pretix-widget-frame-close a");
this.$nextTick(function () {
btn?.focus();
});
} else {
this.prevActiveElement?.focus();
}
},
error_message: function (newValue) {
if (newValue) {
this.prevActiveElement = document.activeElement;
} else {
this.prevActiveElement?.focus();
}
},
} }
}); });
app.$root.overlay = framechild; app.$root.overlay = framechild;
@@ -2228,7 +2232,6 @@ var create_widget = function (element, html_id=null) {
var items = element.attributes.items ? element.attributes.items.value : null; var items = element.attributes.items ? element.attributes.items.value : null;
var variations = element.attributes.variations ? element.attributes.variations.value : null; var variations = element.attributes.variations ? element.attributes.variations.value : null;
var categories = element.attributes.categories ? element.attributes.categories.value : null; var categories = element.attributes.categories ? element.attributes.categories.value : null;
var single_item_select = element.getAttribute("single-item-select") || "checkbox";
for (var i = 0; i < element.attributes.length; i++) { for (var i = 0; i < element.attributes.length; i++) {
var attrib = element.attributes[i]; var attrib = element.attributes[i];
if (attrib.name.match(/^data-.*$/)) { if (attrib.name.match(/^data-.*$/)) {
@@ -2275,8 +2278,6 @@ var create_widget = function (element, html_id=null) {
variation_filter: variations, variation_filter: variations,
voucher_code: voucher, voucher_code: voucher,
display_net_prices: false, display_net_prices: false,
use_native_spinners: false,
single_item_select: single_item_select,
voucher_explanation_text: null, voucher_explanation_text: null,
show_variations_expanded: !!variations, show_variations_expanded: !!variations,
skip_ssl: skip_ssl, skip_ssl: skip_ssl,

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,5 @@
// before variables.scss because it only affects presale, not control // before variables.scss because it only affects presale, not control
$body-bg: #f5f5f5 !default; $body-bg: #f5f5f5 !default;
$input-border: #949494;
$font-size-base: 0.875rem !default;/* 14px/16px = 0.875rem */ $font-size-base: 0.875rem !default;/* 14px/16px = 0.875rem */

View File

@@ -9,7 +9,7 @@
.pretix-widget, .pretix-widget-alert-box { .pretix-widget, .pretix-widget-alert-box {
a { a {
color: $link-color; color: $link-color;
text-decoration: none; text-decoration: underline;
&:hover, &:hover,
&:focus { &:focus {
@@ -17,9 +17,9 @@
text-decoration: $link-hover-decoration; text-decoration: $link-hover-decoration;
} }
&:focus { &:focus {
outline: thin dotted; outline: 2px solid $brand-primary;
outline: 5px auto -webkit-focus-ring-color; outline-offset: 2px;
outline-offset: -2px; z-index: 999;
} }
} }
img { img {
@@ -57,6 +57,9 @@
&.focus { &.focus {
text-decoration: none; text-decoration: none;
@include tab-focus; @include tab-focus;
outline: 2px solid $brand-primary;
outline-offset: 2px;
z-index: 999;
} }
} }
&.disabled, &.disabled,
@@ -72,6 +75,7 @@
} }
label.pretix-widget-btn-checkbox { label.pretix-widget-btn-checkbox {
@include button-variant($btn-default-color, $btn-default-bg, $btn-default-border, darken($btn-default-bg, 10%), darken($btn-default-border, 25%), darken($btn-default-border, 12%)); @include button-variant($btn-default-color, $btn-default-bg, $btn-default-border, darken($btn-default-bg, 10%), darken($btn-default-border, 25%), darken($btn-default-border, 12%));
border-radius: $input-border-radius;
border-width: 1px; border-width: 1px;
border-style: solid; border-style: solid;
position: relative; position: relative;
@@ -88,6 +92,11 @@
background-color: #e6e6e6; background-color: #e6e6e6;
border-color: #adadad; border-color: #adadad;
} }
&:focus-within {
outline: 2px solid $brand-primary;
outline-offset: 2px;
z-index: 999;
}
} }
.pretix-widget-icon-cart { .pretix-widget-icon-cart {
display: inline-block; display: inline-block;
@@ -114,9 +123,9 @@
$color-rgba: rgba(red($input-border-focus), green($input-border-focus), blue($input-border-focus), .6); $color-rgba: rgba(red($input-border-focus), green($input-border-focus), blue($input-border-focus), .6);
&:focus { &:focus {
border-color: $input-border-focus; outline: 2px solid $brand-primary;
outline: 0; outline-offset: 2px;
@include box-shadow(inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px $color-rgba); z-index: 999;
} }
} }
input[type=number] { input[type=number] {
@@ -151,7 +160,6 @@
border-radius: $input-border-radius; border-radius: $input-border-radius;
.pretix-widget-resume-button { .pretix-widget-resume-button {
float: right;
margin-left: 10px; margin-left: 10px;
} }
@@ -160,6 +168,9 @@
} }
.pretix-widget-info-message { .pretix-widget-info-message {
display: flex;
justify-content: space-between;
align-items: flex-end;
padding: 10px; padding: 10px;
text-align: left; text-align: left;
margin: 10px 0; margin: 10px 0;
@@ -168,6 +179,15 @@
color: $state-info-text; color: $state-info-text;
border-radius: $alert-border-radius; border-radius: $alert-border-radius;
} }
&.pretix-widget-mobile {
.pretix-widget-info-message {
flex-direction: column;
}
.pretix-widget-resume-button {
margin-top: 10px;
margin-left: 0;
}
}
.pretix-widget-error-message { .pretix-widget-error-message {
padding: 10px; padding: 10px;
@@ -352,12 +372,15 @@
font-size: 12px; font-size: 12px;
} }
.pretix-widget-item-picture { .pretix-widget-item-picture-link {
width: 60px; width: 60px;
height: 60px; height: 60px;
margin-right: 10px; margin-right: 10px;
float: left; float: left;
} }
.pretix-widget-item-picture {
max-width: 100%;
}
.pretix-widget-action { .pretix-widget-action {
margin-left: 75%; margin-left: 75%;
@@ -431,6 +454,17 @@
.pretix-widget-item-availability-col { .pretix-widget-item-availability-col {
text-align: center; text-align: center;
.pretix-widget-collapse-indicator {
width: 100%;
border: 1px solid $input-border;
border-radius: $input-border-radius;
height: $input-height-base;
padding: $padding-base-vertical $padding-base-horizontal;
color: $input-color;
background-color: $input-bg;
}
.pretix-widget-collapse-indicator::before { .pretix-widget-collapse-indicator::before {
content: ""; content: "";
display: inline-block; display: inline-block;
@@ -514,6 +548,10 @@
flex-wrap: wrap; flex-wrap: wrap;
color: $text-color; color: $text-color;
&:has(.pretix-widget-event-list-entry-availability) {
text-decoration: none;
}
&:hover, &:active, &:focus { &:hover, &:active, &:focus {
background: $gray-lighter; background: $gray-lighter;
text-decoration: none; text-decoration: none;
@@ -540,41 +578,59 @@
padding: 7px 5px 3px; padding: 7px 5px 3px;
box-sizing: border-box; box-sizing: border-box;
span { span {
position: relative;
display: inline; display: inline;
padding: 2px 6px 3px; padding: 6px 6px 4px 17px;
font-size: 75%; font-size: 75%;
font-weight: bold; font-weight: bold;
line-height: 1; line-height: 1;
color: #fff; color: var(--status-text-color, #000);
background-color: var(--status-bg-color, #fff);
border: 1px solid var(--status-border-color, #000);
text-align: center; text-align: center;
white-space: nowrap; white-space: nowrap;
vertical-align: baseline; vertical-align: baseline;
border-radius: 4px; border-radius: 4px;
} }
span:before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 11px;
height: 100%;
background: var(--status-border-color, #000);
}
} }
} }
.pretix-widget-event-availability-orange .pretix-widget-event-list-entry-availability span, .pretix-widget-event-availability-orange,
.pretix-widget-event-availability-orange.pretix-widget-event-calendar-event { .pretix-widget-day-availability-orange {
background-color: $brand-warning; --status-bg-color: #{$alert-warning-bg};
--status-text-color: #{$alert-warning-text};
--status-border-color: #{$alert-warning-border};
} }
.pretix-widget-event-availability-none .pretix-widget-event-list-entry-availability span, .pretix-widget-event-availability-none,
.pretix-widget-event-availability-none.pretix-widget-event-calendar-event { .pretix-widget-day-availability-none {
background-color: $brand-primary; --status-bg-color: #{$alert-primary-bg};
--status-text-color: #{$alert-primary-text};
--status-border-color: #{$alert-primary-border};
} }
.pretix-widget-event-availability-green .pretix-widget-event-list-entry-availability span, .pretix-widget-event-availability-green,
.pretix-widget-event-availability-green.pretix-widget-event-calendar-event { .pretix-widget-day-availability-green {
background-color: $brand-success; --status-bg-color: #{$alert-success-bg};
--status-text-color: #{$alert-success-text};
--status-border-color: #{$alert-success-border};
} }
.pretix-widget-event-availability-red .pretix-widget-event-list-entry-availability span, .pretix-widget-event-availability-red,
.pretix-widget-event-availability-red.pretix-widget-event-calendar-event { .pretix-widget-day-availability-red {
background-color: $brand-danger; --status-bg-color: #{$alert-danger-bg};
--status-text-color: #{$alert-danger-text};
--status-border-color: #{$alert-danger-border};
} }
.pretix-widget-event-availability-low .pretix-widget-event-list-entry-availability span { .pretix-widget-event-list .pretix-widget-event-availability-low .pretix-widget-event-list-entry-availability span:before,
border-left: 10px solid $brand-warning; .pretix-widget-event-calendar .pretix-widget-event-availability-low.pretix-widget-event-calendar-event:before {
} background: linear-gradient(to bottom, var(--pretix-brand-warning) 1em, var(--status-border-color) 2.5em);
.pretix-widget-event-availability-low.pretix-widget-event-calendar-event {
border-right: 10px solid $brand-warning;
} }
.pretix-widget-event-calendar { .pretix-widget-event-calendar {
@@ -587,7 +643,7 @@
.pretix-widget-event-week-col { .pretix-widget-event-week-col {
flex: 1; flex: 1;
margin: 0 5px; margin: 0;
&:first-child { &:first-child {
margin-left: 0; margin-left: 0;
@@ -595,6 +651,12 @@
&:last-child { &:last-child {
margin-right: 0; margin-right: 0;
} }
&:nth-child(even) {
background-color: $table-bg-accent;
}
.pretix-widget-event-calendar-events {
margin: 4px;
}
} }
} }
@@ -616,31 +678,62 @@
} }
} }
.pretix-widget-event-calendar-event { .pretix-widget-event-calendar-event {
position: relative;
display: block; display: block;
border-radius: 4px; border-radius: 4px;
border: 1px solid var(--status-border-color, #000);
background-color: var(--status-bg-color, #fff);
color: var(--status-text-color, #000);
padding: 5px; padding: 5px;
color: white; padding-left: 17px;
cursor: pointer; cursor: pointer;
margin-bottom: 5px; margin-bottom: 5px;
&:before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 11px;
height: 100%;
background: var(--status-border-color, #000);
}
&:last-child { &:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
&:hover { text-decoration: none;
text-decoration: none;
}
} }
.pretix-widget-event-calendar-table { .pretix-widget-event-calendar-table {
width: 100%; width: 100%;
border-spacing: 0;
th, td { th, td {
width: 14.285714285714286%; width: 14.285714285714286%;
vertical-align: top; vertical-align: top;
padding: 10px 5px; padding: 4px;
border-bottom: 1px solid $table-border-color;
}
th {
border-bottom-width: 2px;
color: $text-muted;
}
td:has(.pretix-widget-event-calendar-day):nth-child(even) {
background: $table-bg-accent;
} }
} }
.pretix-widget-event-calendar-day { .pretix-widget-event-calendar-day {
font-weight: bold; font-weight: bold;
font-size: 86%;
padding: 5px 5px 1em;
}
.pretix-widget-event-week-table .pretix-widget-event-calendar-day {
padding-bottom: 5px;
background-color: #fff;
border-bottom: 2px solid $table-border-color;
color: $text-muted;
font-size: 100%;
} }
} }
@@ -656,9 +749,24 @@
.pretix-widget-event-list-filter-form { .pretix-widget-event-list-filter-form {
display: flex; .pretix-widget-event-list-filter-fieldset {
flex-direction: row; display: flex;
align-items: end; flex-direction: row;
align-items: end;
border: none;
padding: 0;
margin: 0;
> legend {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}
}
margin-bottom: 15px; margin-bottom: 15px;
.pretix-widget-event-list-filter-field { .pretix-widget-event-list-filter-field {
@@ -667,7 +775,7 @@
margin: 0 15px 0 0; margin: 0 15px 0 0;
label { label {
display: inline-block; display: block;
font-weight: bold; font-weight: bold;
margin-bottom: 5px; margin-bottom: 5px;
} }
@@ -682,7 +790,9 @@
} }
} }
.pretix-widget.pretix-widget-mobile .pretix-widget-event-list-filter-form { .pretix-widget.pretix-widget-mobile .pretix-widget-event-list-filter-form {
display: block; .pretix-widget-event-list-filter-fieldset {
display: block;
}
.pretix-widget-event-list-filter-field { .pretix-widget-event-list-filter-field {
display: block; display: block;
@@ -701,24 +811,120 @@
transform: scale(1); transform: scale(1);
} }
} }
.pretix-widget-alert-holder {
position: fixed; .pretix-widget-visibility-hidden {
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.8);
z-index: 16777271;
visibility: hidden; visibility: hidden;
opacity: 0; }
transition: opacity 0.5s; /* do not animate visibility or we'll have a flashing thing on load */ .pretix-widget-shake-once {
animation: pretix-widget-shake .2s;
transform: translate3d(0, 0, 0);
backface-visibility: hidden;
}
&.pretix-widget-alert-shown { @keyframes pretix-widget-shake {
visibility: visible; 0% { transform: skewX(0deg); }
opacity: 1; 20% { transform: skewX(-5deg); }
transition: opacity 0.5s, visibility 0.5s; 40% { transform: skewX(5deg); }
60% { transform: skewX(-5deg); }
80% { transform: skewX(5deg); }
100% { transform: skewX(0deg); }
}
.pretix-widget-alert-holder,
.pretix-widget-frame-holder,
.pretix-widget-lightbox-holder {
border: none;
background: transparent;
overflow: visible;
&::backdrop {
background: rgba(255, 255, 255, 0.8);
} }
&:focus {
outline: 2px solid $brand-primary;
outline-offset: 2px;
}
}
.pretix-widget-frame-isloading:focus {
outline: none;
svg {
outline: 2px solid $brand-primary;
border-radius: 100%;
}
}
.pretix-widget-frame-loading p,
.pretix-widget-lightbox-loading p {
text-align: center;
width: 256px;
margin: 0 auto;
color: $brand-danger;
}
.pretix-widget-frame-loading svg,
.pretix-widget-lightbox-loading svg {
margin: 40px;
-webkit-animation: pretix-widget-spin 6s linear infinite;
-moz-animation: pretix-widget-spin 6s linear infinite;
animation: pretix-widget-spin 6s linear infinite;
}
.pretix-widget-frame-close,
.pretix-widget-lightbox-close {
position: absolute;
top: -12px;
right: -12px;
z-index: 2;
}
.pretix-widget-frame-close button,
.pretix-widget-lightbox-close button {
color: white;
cursor: pointer;
font-weight: bold;
font-family: sans-serif;
text-decoration: none;
padding: 4px 0;
display: inline-block;
line-height: 16px;
border: none;
background: none;
width: 24px;
height: 24px;
background: $brand-primary;
border-radius: 12px;
-moz-border-radius: 12px;
-webkit-border-radius: 12px;
text-align: center;
&:focus {
outline: 2px solid $brand-primary;
outline-offset: 2px;
}
}
.pretix-widget-frame-close svg,
.pretix-widget-lightbox-close svg {
display: inline-block;
border: none;
}
.pretix-widget-frame-inner,
.pretix-widget-lightbox-inner,
.pretix-widget-alert-box {
position: relative;
background: white;
border-radius: 5px 5px 5px 5px;
-moz-border-radius: 5px 5px 5px 5px;
-webkit-border-radius: 5px 5px 5px 5px;
box-shadow: 0 4px 18px 0 rgba(0, 0, 0, 0.1), 0 6px 20px 0 rgba(0, 0, 0, 0.09);
-webkit-box-shadow: 0 4px 18px 0 rgba(0, 0, 0, 0.1), 0 6px 20px 0 rgba(0, 0, 0, 0.09);
-moz-box-shadow: 0 4px 18px 0 rgba(0, 0, 0, 0.1), 0 6px 20px 0 rgba(0, 0, 0, 0.09);
box-sizing: border-box;
padding: 10px;
}
.pretix-widget-alert-holder {
.bounce-enter-active { .bounce-enter-active {
animation: pretix-widget-bounce-in .5s; animation: pretix-widget-bounce-in .5s;
} }
@@ -727,19 +933,6 @@
} }
.pretix-widget-alert-box { .pretix-widget-alert-box {
position: fixed;
left: 50%;
width: 600px;
margin-left: -300px;
top: 100px;
background: white;
border-radius: 5px 5px 5px 5px;
-moz-border-radius: 5px 5px 5px 5px;
-webkit-border-radius: 5px 5px 5px 5px;
box-shadow: 0 4px 18px 0 rgba(0, 0, 0, 0.1), 0 6px 20px 0 rgba(0, 0, 0, 0.09);
-webkit-box-shadow: 0 4px 18px 0 rgba(0, 0, 0, 0.1), 0 6px 20px 0 rgba(0, 0, 0, 0.09);
-moz-box-shadow: 0 4px 18px 0 rgba(0, 0, 0, 0.1), 0 6px 20px 0 rgba(0, 0, 0, 0.09);
box-sizing: border-box;
padding: 42px 20px 20px 20px; padding: 42px 20px 20px 20px;
text-align: center; text-align: center;
font-size: 20px; font-size: 20px;
@@ -752,197 +945,47 @@
} }
} }
.pretix-widget-alert-icon { .pretix-widget-alert-icon {
position: fixed; position: absolute;
left: 50%; left: 50%;
width: 64px; width: 64px;
margin-left: -32px; margin-left: -32px;
top: 68px; top: -20px;
} }
} }
.pretix-widget-frame-holder {
position: fixed;
left: 0;
top: 0; .pretix-widget-frame-inner {
width: 100%; width: 80vw;
height: 100%; height: 80vh;
background: rgba(255, 255, 255, 0.8); }
z-index: 16777271; .pretix-widget-frame-inner iframe {
width: 100% !important;
height: 100% !important;
}
.pretix-widget-lightbox-inner {
max-width: 90vw;
max-height: 90vh;
}
.pretix-widget-lightbox-isloading .pretix-widget-lightbox-inner {
visibility: hidden; visibility: hidden;
opacity: 0; }
transition: opacity 0.5s, visibility 0.5s; .pretix-widget-lightbox-image {
margin: 0;
.pretix-widget-frame-loading { padding: 0;
text-align: center; text-align: center;
display: flex; }
align-items: center; .pretix-widget-lightbox-image img {
justify-content: center; max-width: 80vw;
height: 100%; max-height: 80vh;
width: 100%; object-fit: scale-down;
position: fixed; }
left: 0; .pretix-widget-lightbox-image figcaption {
top: 0; margin: 0.5em 0 0;
}
.pretix-widget-frame-loading svg {
margin: 40px;
-webkit-animation: pretix-widget-spin 6s linear infinite;
-moz-animation: pretix-widget-spin 6s linear infinite;
animation: pretix-widget-spin 6s linear infinite;
}
&.pretix-widget-frame-shown {
visibility: visible;
opacity: 1;
transition: opacity 0.5s, visibility 0.5s;
}
.pretix-widget-frame-inner {
position: fixed;
left: 10%;
width: 80%;
height: 80%;
top: 10%;
background: white;
border-radius: 5px 5px 5px 5px;
-moz-border-radius: 5px 5px 5px 5px;
-webkit-border-radius: 5px 5px 5px 5px;
box-shadow: 0 4px 18px 0 rgba(0, 0, 0, 0.1), 0 6px 20px 0 rgba(0, 0, 0, 0.09);
-webkit-box-shadow: 0 4px 18px 0 rgba(0, 0, 0, 0.1), 0 6px 20px 0 rgba(0, 0, 0, 0.09);
-moz-box-shadow: 0 4px 18px 0 rgba(0, 0, 0, 0.1), 0 6px 20px 0 rgba(0, 0, 0, 0.09);
box-sizing: border-box;
padding: 10px;
}
.pretix-widget-frame-close {
position: fixed;
right: 10%;
top: 10%;
width: 24px;
height: 24px;
background: $brand-primary;
margin: -12px -12px 0 0;
border-radius: 12px;
-moz-border-radius: 12px;
-webkit-border-radius: 12px;
text-align: center;
}
.pretix-widget-frame-close a {
color: white;
font-weight: bold;
font-family: sans-serif;
text-decoration: none;
padding: 4px 0;
display: inline-block;
line-height: 16px;
}
.pretix-widget-frame-close svg {
display: inline-block;
border: none;
}
.pretix-widget-frame-inner iframe {
width: 100% !important;
height: 100% !important;
}
} }
.pretix-widget-lightbox-holder {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.8);
z-index: 16777271;
visibility: hidden;
opacity: 0;
transition: opacity 0.5s, visibility 0.5s;
display: flex;
align-items: center;
justify-content: center;
.pretix-widget-lightbox-loading svg {
margin: 40px;
-webkit-animation: pretix-widget-spin 6s linear infinite;
-moz-animation: pretix-widget-spin 6s linear infinite;
animation: pretix-widget-spin 6s linear infinite;
}
&.pretix-widget-lightbox-shown {
visibility: visible;
opacity: 1;
transition: opacity 0.5s, visibility 0.5s;
}
.pretix-widget-lightbox-inner {
position: relative;
background: white;
border-radius: 5px 5px 5px 5px;
-moz-border-radius: 5px 5px 5px 5px;
-webkit-border-radius: 5px 5px 5px 5px;
box-shadow: 0 4px 18px 0 rgba(0, 0, 0, 0.1), 0 6px 20px 0 rgba(0, 0, 0, 0.09);
-webkit-box-shadow: 0 4px 18px 0 rgba(0, 0, 0, 0.1), 0 6px 20px 0 rgba(0, 0, 0, 0.09);
-moz-box-shadow: 0 4px 18px 0 rgba(0, 0, 0, 0.1), 0 6px 20px 0 rgba(0, 0, 0, 0.09);
box-sizing: border-box;
padding: 10px;
max-width: 90%;
max-height: 90%;
}
&.pretix-widget-lightbox-isloading .pretix-widget-lightbox-inner {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0,0,0,0);
border: 0;
}
.pretix-widget-lightbox-image {
margin: 0;
padding: 0;
text-align: center;
}
.pretix-widget-lightbox-image img {
max-width: 80vw;
max-height: 80vh;
object-fit: scale-down;
}
.pretix-widget-lightbox-image figcaption {
margin: 0.5em 0 0;
}
.pretix-widget-lightbox-close {
position: absolute;
right: -12px;
top: -12px;
width: 24px;
height: 24px;
background: $brand-primary;
margin: 0;
border: none;
border-radius: 12px;
-moz-border-radius: 12px;
-webkit-border-radius: 12px;
text-align: center;
color: white;
font-weight: bold;
font-family: sans-serif;
text-decoration: none;
padding: 4px 0;
display: inline-block;
line-height: 16px;
cursor: pointer;
}
.pretix-widget-lightbox-close svg {
display: inline-block;
border: none;
}
}
.pretix-widget-primary-color { .pretix-widget-primary-color {
/* in SVG */ /* in SVG */
@@ -1015,22 +1058,14 @@
display: block; display: block;
} }
} }
td.pretix-widget-has-events { td.pretix-widget-has-events .pretix-widget-event-calendar-day {
background: $brand-primary; background: var(--status-bg-color, #fff);
color: white; color: var(--status-text-color, #000);
border: 1px solid var(--status-border-color, inherit);
border-top-width: 11px;
padding-bottom: 5px;
border-radius: $input-border-radius;
cursor: pointer; cursor: pointer;
&.pretix-widget-day-availability-red {
background: $brand-danger;
}
&.pretix-widget-day-availability-green {
background: $brand-success;
}
&.pretix-widget-day-availability-low {
border-right: 5px solid $brand-warning;
}
&.pretix-widget-day-availability-orange {
background: $brand-warning;
}
} }
.pretix-widget-event-calendar-head { .pretix-widget-event-calendar-head {
@@ -1048,41 +1083,3 @@
} }
} }
@media (min-width: 1200px) {
.pretix-widget-frame-holder {
.pretix-widget-frame-inner {
left: 50%;
margin-left: -540px;
width: 1080px;
}
.pretix-widget-frame-close {
left: 50%;
margin-left: 528px;
}
}
}
@media (max-width: 800px) {
.pretix-widget-frame-holder .pretix-widget-frame-inner {
left: 0;
width: 100%;
height: 100%;
top: 0;
background: $brand-primary;
border-radius: 0;
-moz-border-radius: 0;
-webkit-border-radius: 0;
box-shadow: none;
-webkit-box-shadow: none;
-moz-box-shadow: none;
padding: 40px 0 0 0;
}
.pretix-widget-frame-holder .pretix-widget-frame-close {
right: 20px;
top: 20px;
background: white;
svg path {
fill: $brand-primary;
}
}
}

File diff suppressed because it is too large Load Diff